blob: 58d79b25a3e296180c5d6f71591c4aab2ac3ca25 [file] [log] [blame]
Matthew Barth0c4b1572020-10-22 14:39:46 -05001/**
2 * Copyright © 2020 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#pragma once
17
Matthew Barthd9cb63b2021-03-24 14:36:49 -050018#include "../zone.hpp"
Matthew Barthbfd7e1b2021-02-04 14:25:47 -060019#include "config_base.hpp"
Matthew Barth12cb1252021-03-08 16:47:30 -060020#include "group.hpp"
Matthew Barth0c4b1572020-10-22 14:39:46 -050021
22#include <fmt/format.h>
23
24#include <nlohmann/json.hpp>
25#include <phosphor-logging/log.hpp>
26
Matthew Barthb5811a62021-06-30 14:32:21 -050027#include <algorithm>
Matthew Barth0c4b1572020-10-22 14:39:46 -050028#include <functional>
29#include <iterator>
30#include <map>
31#include <memory>
32#include <numeric>
33
34namespace phosphor::fan::control::json
35{
36
37using json = nlohmann::json;
38using namespace phosphor::logging;
39
40/**
Matthew Barthbfd7e1b2021-02-04 14:25:47 -060041 * @class ActionParseError - A parsing error exception
42 *
43 * A parsing error exception that can be used to terminate the application
44 * due to not being able to successfully parse a configured action.
45 */
46class ActionParseError : public std::runtime_error
47{
48 public:
49 ActionParseError() = delete;
50 ActionParseError(const ActionParseError&) = delete;
51 ActionParseError(ActionParseError&&) = delete;
52 ActionParseError& operator=(const ActionParseError&) = delete;
53 ActionParseError& operator=(ActionParseError&&) = delete;
54 ~ActionParseError() = default;
55
56 /**
57 * @brief Action parsing error object
58 *
59 * When parsing an action from the JSON configuration, any critical
60 * attributes that fail to be parsed for an action can throw an
61 * ActionParseError exception to log the parsing failure details and
62 * terminate the application.
63 *
64 * @param[in] name - Name of the action
65 * @param[in] details - Additional details of the parsing error
66 */
67 ActionParseError(const std::string& name, const std::string& details) :
68 std::runtime_error(
69 fmt::format("Failed to parse action {} [{}]", name, details)
70 .c_str())
71 {}
72};
73
74/**
Matthew Barth0c4b1572020-10-22 14:39:46 -050075 * @brief Function used in creating action objects
76 *
77 * @param[in] jsonObj - JSON object for the action
Matthew Barth19c77492021-04-08 10:06:06 -050078 * @param[in] groups - Groups of dbus objects the action uses
79 * @param[in] zones - Zones the action runs against
Matthew Barth0c4b1572020-10-22 14:39:46 -050080 *
Matthew Barth19c77492021-04-08 10:06:06 -050081 * Creates an action object given the JSON configuration, list of groups and
82 * sets the zones the action should run against.
Matthew Barth0c4b1572020-10-22 14:39:46 -050083 */
84template <typename T>
Matthew Barth19c77492021-04-08 10:06:06 -050085std::unique_ptr<T>
86 createAction(const json& jsonObj, const std::vector<Group>& groups,
87 std::vector<std::reference_wrapper<Zone>>& zones)
Matthew Barth0c4b1572020-10-22 14:39:46 -050088{
Matthew Barth19c77492021-04-08 10:06:06 -050089 // Create the action and set its list of zones
90 auto action = std::make_unique<T>(jsonObj, groups);
91 action->setZones(zones);
92 return action;
Matthew Barth0c4b1572020-10-22 14:39:46 -050093}
94
95/**
96 * @class ActionBase - Base action object
97 *
98 * Base class for fan control's event actions
99 */
Matthew Barthbfd7e1b2021-02-04 14:25:47 -0600100class ActionBase : public ConfigBase
Matthew Barth0c4b1572020-10-22 14:39:46 -0500101{
102 public:
103 ActionBase() = delete;
104 ActionBase(const ActionBase&) = delete;
105 ActionBase(ActionBase&&) = delete;
106 ActionBase& operator=(const ActionBase&) = delete;
107 ActionBase& operator=(ActionBase&&) = delete;
108 virtual ~ActionBase() = default;
Matthew Barth41a34082021-01-27 15:31:48 -0600109
110 /**
111 * @brief Base action object
112 *
Matthew Barth19c77492021-04-08 10:06:06 -0500113 * @param[in] jsonObj - JSON object containing name and any profiles
114 * @param[in] groups - Groups of dbus objects the action uses
115 *
Matthew Barth41a34082021-01-27 15:31:48 -0600116 * All actions derived from this base action object must be given a name
Matthew Barthbfd7e1b2021-02-04 14:25:47 -0600117 * that uniquely identifies the action. Optionally, a configured action can
118 * have a list of explicit profiles it should be included in, otherwise
119 * always include the action where no profiles are given.
Matthew Barth41a34082021-01-27 15:31:48 -0600120 */
Matthew Barth19c77492021-04-08 10:06:06 -0500121 ActionBase(const json& jsonObj, const std::vector<Group>& groups) :
122 ConfigBase(jsonObj), _groups(groups)
Matthew Barth0c4b1572020-10-22 14:39:46 -0500123 {}
124
125 /**
Matthew Bartheebde062021-04-14 12:48:52 -0500126 * @brief Get the groups configured on the action
127 *
128 * @return List of groups
129 */
130 inline const auto& getGroups() const
131 {
132 return _groups;
133 }
134
135 /**
Matthew Barth19c77492021-04-08 10:06:06 -0500136 * @brief Set the zones the action is run against
137 *
138 * @param[in] zones - Zones the action runs against
139 *
140 * By default, the zones are set when the action object is created
141 */
142 virtual void setZones(std::vector<std::reference_wrapper<Zone>>& zones)
143 {
144 _zones = zones;
145 }
146
147 /**
Matthew Barthb5811a62021-06-30 14:32:21 -0500148 * @brief Add a zone to the list of zones the action is run against if its
149 * not already there
150 *
151 * @param[in] zone - Zone to add
152 */
153 virtual void addZone(Zone& zone)
154 {
155 auto itZone =
156 std::find_if(_zones.begin(), _zones.end(),
157 [&zone](std::reference_wrapper<Zone>& z) {
158 return z.get().getName() == zone.getName();
159 });
160 if (itZone == _zones.end())
161 {
162 _zones.emplace_back(std::reference_wrapper<Zone>(zone));
163 }
164 }
165
166 /**
Matthew Barth41a34082021-01-27 15:31:48 -0600167 * @brief Run the action
Matthew Barth0c4b1572020-10-22 14:39:46 -0500168 *
Matthew Barth41a34082021-01-27 15:31:48 -0600169 * Run the action function associated to the derived action object
Matthew Barth6d2476c2021-04-08 10:48:57 -0500170 * that performs a specific tasks on a zone configured by a user.
Matthew Barth0c4b1572020-10-22 14:39:46 -0500171 *
Matthew Barth41a34082021-01-27 15:31:48 -0600172 * @param[in] zone - Zone to run the action on
Matthew Barth0c4b1572020-10-22 14:39:46 -0500173 */
Matthew Barth6d2476c2021-04-08 10:48:57 -0500174 virtual void run(Zone& zone) = 0;
Matthew Barth19c77492021-04-08 10:06:06 -0500175
176 /**
177 * @brief Trigger the action to run against all of its zones
178 *
179 * This is the function used by triggers to run the actions against all the
180 * zones that were configured for the action to run against.
181 */
182 void run()
183 {
Matthew Barth19c77492021-04-08 10:06:06 -0500184 std::for_each(_zones.begin(), _zones.end(),
Matthew Barth6d2476c2021-04-08 10:48:57 -0500185 [this](Zone& zone) { this->run(zone); });
Matthew Barth19c77492021-04-08 10:06:06 -0500186 }
187
188 protected:
189 /* Groups configured on the action */
190 const std::vector<Group> _groups;
191
192 private:
193 /* Zones configured on the action */
194 std::vector<std::reference_wrapper<Zone>> _zones;
Matthew Barth0c4b1572020-10-22 14:39:46 -0500195};
196
197/**
198 * @class ActionFactory - Factory for actions
199 *
200 * Factory that registers and retrieves actions based on a given name.
201 */
202class ActionFactory
203{
204 public:
205 ActionFactory() = delete;
206 ActionFactory(const ActionFactory&) = delete;
207 ActionFactory(ActionFactory&&) = delete;
208 ActionFactory& operator=(const ActionFactory&) = delete;
209 ActionFactory& operator=(ActionFactory&&) = delete;
210 ~ActionFactory() = default;
211
212 /**
213 * @brief Registers an action
214 *
215 * Registers an action as being available for configuration use. The action
216 * is registered by its name and a function used to create the action
217 * object. An action fails to be registered when another action of the same
218 * name has already been registered. Actions with the same name would cause
219 * undefined behavior, therefore are not allowed.
220 *
221 * Actions are registered prior to starting main().
222 *
223 * @param[in] name - Name of the action to register
224 *
225 * @return The action was registered, otherwise an exception is thrown.
226 */
227 template <typename T>
228 static bool regAction(const std::string& name)
229 {
230 auto it = actions.find(name);
231 if (it == actions.end())
232 {
233 actions[name] = &createAction<T>;
234 }
235 else
236 {
237 log<level::ERR>(
238 fmt::format("Action '{}' is already registered", name).c_str());
239 throw std::runtime_error("Actions with the same name found");
240 }
241
242 return true;
243 }
244
245 /**
246 * @brief Gets a registered action's object
247 *
248 * Gets a registered action's object of a given name from the JSON
249 * configuration data provided.
250 *
251 * @param[in] name - Name of the action to create/get
252 * @param[in] jsonObj - JSON object for the action
Matthew Barth19c77492021-04-08 10:06:06 -0500253 * @param[in] groups - Groups of dbus objects the action uses
254 * @param[in] zones - Zones the action runs against
Matthew Barth0c4b1572020-10-22 14:39:46 -0500255 *
256 * @return Pointer to the action object.
257 */
Matthew Barth46b34482021-04-06 11:27:23 -0500258 static std::unique_ptr<ActionBase>
259 getAction(const std::string& name, const json& jsonObj,
260 const std::vector<Group>& groups,
261 std::vector<std::reference_wrapper<Zone>>&& zones)
Matthew Barth0c4b1572020-10-22 14:39:46 -0500262 {
263 auto it = actions.find(name);
264 if (it != actions.end())
265 {
Matthew Barth19c77492021-04-08 10:06:06 -0500266 return it->second(jsonObj, groups, zones);
Matthew Barth0c4b1572020-10-22 14:39:46 -0500267 }
268 else
269 {
270 // Construct list of available actions
271 auto acts = std::accumulate(
272 std::next(actions.begin()), actions.end(),
273 actions.begin()->first, [](auto list, auto act) {
274 return std::move(list) + ", " + act.first;
275 });
276 log<level::ERR>(
277 fmt::format("Action '{}' is not registered", name).c_str(),
278 entry("AVAILABLE_ACTIONS=%s", acts.c_str()));
279 throw std::runtime_error("Unsupported action name given");
280 }
281 }
282
283 private:
284 /* Map to store the available actions and their creation functions */
Matthew Barth19c77492021-04-08 10:06:06 -0500285 static inline std::map<std::string,
286 std::function<std::unique_ptr<ActionBase>(
287 const json&, const std::vector<Group>&,
288 std::vector<std::reference_wrapper<Zone>>&)>>
Matthew Barth0c4b1572020-10-22 14:39:46 -0500289 actions;
290};
291
292/**
293 * @class ActionRegister - Registers an action class
294 *
295 * Base action registration class that is extended by an action object so
296 * that action is registered and available for use.
297 */
298template <typename T>
299class ActionRegister
300{
301 public:
302 ActionRegister(const ActionRegister&) = delete;
303 ActionRegister(ActionRegister&&) = delete;
304 ActionRegister& operator=(const ActionRegister&) = delete;
305 ActionRegister& operator=(ActionRegister&&) = delete;
306 virtual ~ActionRegister() = default;
307 ActionRegister()
308 {
309 // Templates instantiated when used, need to assign a value
310 // here so the compiler doesnt remove it
311 registered = true;
312 }
313
314 private:
315 /* Register actions in the factory */
316 static inline bool registered = ActionFactory::regAction<T>(T::name);
317};
318
319} // namespace phosphor::fan::control::json