1 /**
2 * Copyright © 2022 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
18 #include "../utils/flight_recorder.hpp"
19 #include "../zone.hpp"
20 #include "config_base.hpp"
21 #include "group.hpp"
22
23 #include <nlohmann/json.hpp>
24 #include <phosphor-logging/log.hpp>
25
26 #include <algorithm>
27 #include <format>
28 #include <functional>
29 #include <iterator>
30 #include <map>
31 #include <memory>
32 #include <numeric>
33
34 namespace phosphor::fan::control::json
35 {
36
37 using json = nlohmann::json;
38 using namespace phosphor::logging;
39
40 /**
41 * @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 */
46 class 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 */
ActionParseError(const std::string & name,const std::string & details)67 ActionParseError(const std::string& name, const std::string& details) :
68 std::runtime_error(
69 std::format("Failed to parse action {} [{}]", name, details)
70 .c_str())
71 {}
72 };
73
74 /**
75 * @brief Function used in creating action objects
76 *
77 * @param[in] jsonObj - JSON object for the action
78 * @param[in] groups - Groups of dbus objects the action uses
79 * @param[in] zones - Zones the action runs against
80 *
81 * Creates an action object given the JSON configuration, list of groups and
82 * sets the zones the action should run against.
83 */
84 template <typename T>
createAction(const json & jsonObj,const std::vector<Group> & groups,std::vector<std::reference_wrapper<Zone>> & zones)85 std::unique_ptr<T> createAction(
86 const json& jsonObj, const std::vector<Group>& groups,
87 std::vector<std::reference_wrapper<Zone>>& zones)
88 {
89 // 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;
93 }
94
95 /**
96 * @class ActionBase - Base action object
97 *
98 * Base class for fan control's event actions
99 */
100 class ActionBase : public ConfigBase
101 {
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;
109
110 /**
111 * @brief Base action object
112 *
113 * @param[in] jsonObj - JSON object containing name and any profiles
114 * @param[in] groups - Groups of dbus objects the action uses
115 *
116 * All actions derived from this base action object must be given a name
117 * 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.
120 */
ActionBase(const json & jsonObj,const std::vector<Group> & groups)121 ActionBase(const json& jsonObj, const std::vector<Group>& groups) :
122 ConfigBase(jsonObj), _groups(groups),
123 _uniqueName(getName() + "-" + std::to_string(_actionCount++))
124 {}
125
126 /**
127 * @brief Get the groups configured on the action
128 *
129 * @return List of groups
130 */
getGroups() const131 inline const auto& getGroups() const
132 {
133 return _groups;
134 }
135
136 /**
137 * @brief Set the zones the action is run against
138 *
139 * @param[in] zones - Zones the action runs against
140 *
141 * By default, the zones are set when the action object is created
142 */
setZones(std::vector<std::reference_wrapper<Zone>> & zones)143 virtual void setZones(std::vector<std::reference_wrapper<Zone>>& zones)
144 {
145 _zones = zones;
146 }
147
148 /**
149 * @brief Add a zone to the list of zones the action is run against if its
150 * not already there
151 *
152 * @param[in] zone - Zone to add
153 */
addZone(Zone & zone)154 virtual void addZone(Zone& zone)
155 {
156 auto itZone =
157 std::find_if(_zones.begin(), _zones.end(),
158 [&zone](std::reference_wrapper<Zone>& z) {
159 return z.get().getName() == zone.getName();
160 });
161 if (itZone == _zones.end())
162 {
163 _zones.emplace_back(std::reference_wrapper<Zone>(zone));
164 }
165 }
166
167 /**
168 * @brief Run the action
169 *
170 * Run the action function associated to the derived action object
171 * that performs a specific tasks on a zone configured by a user.
172 *
173 * @param[in] zone - Zone to run the action on
174 */
175 virtual void run(Zone& zone) = 0;
176
177 /**
178 * @brief Trigger the action to run against all of its zones
179 *
180 * This is the function used by triggers to run the actions against all the
181 * zones that were configured for the action to run against.
182 */
run()183 void run()
184 {
185 std::for_each(_zones.begin(), _zones.end(),
186 [this](Zone& zone) { this->run(zone); });
187 }
188
189 /**
190 * @brief Returns a unique name for the action.
191 *
192 * @return std::string - The name
193 */
getUniqueName() const194 const std::string& getUniqueName() const
195 {
196 return _uniqueName;
197 }
198
199 /**
200 * @brief Set the name of the owning Event.
201 *
202 * Adds it to the unique name in parentheses. If desired,
203 * the action child classes can do something else with it.
204 *
205 * @param[in] name - The event name
206 */
setEventName(const std::string & name)207 virtual void setEventName(const std::string& name)
208 {
209 if (!name.empty())
210 {
211 _uniqueName += '(' + name + ')';
212 }
213 }
214
215 /**
216 * @brief Dump the action as JSON
217 *
218 * For now just dump its group names
219 *
220 * @return json
221 */
dump() const222 json dump() const
223 {
224 json groups = json::array();
225 std::for_each(_groups.begin(), _groups.end(),
226 [&groups](const auto& group) {
227 groups.push_back(group.getName());
228 });
229 json output;
230 output["groups"] = groups;
231 return output;
232 }
233
234 protected:
235 /**
236 * @brief Logs a message to the flight recorder using
237 * the unique name of the action.
238 *
239 * @param[in] message - The message to log
240 */
record(const std::string & message) const241 void record(const std::string& message) const
242 {
243 FlightRecorder::instance().log(getUniqueName(), message);
244 }
245
246 /* Groups configured on the action */
247 const std::vector<Group> _groups;
248
249 private:
250 /* Zones configured on the action */
251 std::vector<std::reference_wrapper<Zone>> _zones;
252
253 /* Unique name of the action.
254 * It's just the name plus _actionCount at the time of action creation. */
255 std::string _uniqueName;
256
257 /* Running count of all actions */
258 static inline size_t _actionCount = 0;
259 };
260
261 /**
262 * @class ActionFactory - Factory for actions
263 *
264 * Factory that registers and retrieves actions based on a given name.
265 */
266 class ActionFactory
267 {
268 public:
269 ActionFactory() = delete;
270 ActionFactory(const ActionFactory&) = delete;
271 ActionFactory(ActionFactory&&) = delete;
272 ActionFactory& operator=(const ActionFactory&) = delete;
273 ActionFactory& operator=(ActionFactory&&) = delete;
274 ~ActionFactory() = default;
275
276 /**
277 * @brief Registers an action
278 *
279 * Registers an action as being available for configuration use. The action
280 * is registered by its name and a function used to create the action
281 * object. An action fails to be registered when another action of the same
282 * name has already been registered. Actions with the same name would cause
283 * undefined behavior, therefore are not allowed.
284 *
285 * Actions are registered prior to starting main().
286 *
287 * @param[in] name - Name of the action to register
288 *
289 * @return The action was registered, otherwise an exception is thrown.
290 */
291 template <typename T>
regAction(const std::string & name)292 static bool regAction(const std::string& name)
293 {
294 auto it = actions.find(name);
295 if (it == actions.end())
296 {
297 actions[name] = &createAction<T>;
298 }
299 else
300 {
301 log<level::ERR>(
302 std::format("Action '{}' is already registered", name).c_str());
303 throw std::runtime_error("Actions with the same name found");
304 }
305
306 return true;
307 }
308
309 /**
310 * @brief Gets a registered action's object
311 *
312 * Gets a registered action's object of a given name from the JSON
313 * configuration data provided.
314 *
315 * @param[in] name - Name of the action to create/get
316 * @param[in] jsonObj - JSON object for the action
317 * @param[in] groups - Groups of dbus objects the action uses
318 * @param[in] zones - Zones the action runs against
319 *
320 * @return Pointer to the action object.
321 */
getAction(const std::string & name,const json & jsonObj,const std::vector<Group> & groups,std::vector<std::reference_wrapper<Zone>> && zones)322 static std::unique_ptr<ActionBase> getAction(
323 const std::string& name, const json& jsonObj,
324 const std::vector<Group>& groups,
325 std::vector<std::reference_wrapper<Zone>>&& zones)
326 {
327 auto it = actions.find(name);
328 if (it != actions.end())
329 {
330 return it->second(jsonObj, groups, zones);
331 }
332 else
333 {
334 // Construct list of available actions
335 auto acts = std::accumulate(
336 std::next(actions.begin()), actions.end(),
337 actions.begin()->first, [](auto list, auto act) {
338 return std::move(list) + ", " + act.first;
339 });
340 log<level::ERR>(
341 std::format("Action '{}' is not registered", name).c_str(),
342 entry("AVAILABLE_ACTIONS=%s", acts.c_str()));
343 throw std::runtime_error("Unsupported action name given");
344 }
345 }
346
347 private:
348 /* Map to store the available actions and their creation functions */
349 static inline std::map<std::string,
350 std::function<std::unique_ptr<ActionBase>(
351 const json&, const std::vector<Group>&,
352 std::vector<std::reference_wrapper<Zone>>&)>>
353 actions;
354 };
355
356 /**
357 * @class ActionRegister - Registers an action class
358 *
359 * Base action registration class that is extended by an action object so
360 * that action is registered and available for use.
361 */
362 template <typename T>
363 class ActionRegister
364 {
365 public:
366 ActionRegister(const ActionRegister&) = delete;
367 ActionRegister(ActionRegister&&) = delete;
368 ActionRegister& operator=(const ActionRegister&) = delete;
369 ActionRegister& operator=(ActionRegister&&) = delete;
370 virtual ~ActionRegister() = default;
ActionRegister()371 ActionRegister()
372 {
373 // Templates instantiated when used, need to assign a value
374 // here so the compiler doesnt remove it
375 registered = true;
376 }
377
378 private:
379 /* Register actions in the factory */
380 static inline bool registered = ActionFactory::regAction<T>(T::name);
381 };
382
383 } // namespace phosphor::fan::control::json
384