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