1 /**
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 
18 #include "../zone.hpp"
19 #include "config_base.hpp"
20 #include "group.hpp"
21 
22 #include <fmt/format.h>
23 
24 #include <nlohmann/json.hpp>
25 #include <phosphor-logging/log.hpp>
26 
27 #include <algorithm>
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      */
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 /**
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>
85 std::unique_ptr<T>
86     createAction(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      */
121     ActionBase(const json& jsonObj, const std::vector<Group>& groups) :
122         ConfigBase(jsonObj), _groups(groups)
123     {}
124 
125     /**
126      * @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     /**
136      * @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     /**
148      * @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     /**
167      * @brief Run the action
168      *
169      * Run the action function associated to the derived action object
170      * that performs a specific tasks on a zone configured by a user.
171      *
172      * @param[in] zone - Zone to run the action on
173      */
174     virtual void run(Zone& zone) = 0;
175 
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     {
184         std::for_each(_zones.begin(), _zones.end(),
185                       [this](Zone& zone) { this->run(zone); });
186     }
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;
195 };
196 
197 /**
198  * @class ActionFactory - Factory for actions
199  *
200  * Factory that registers and retrieves actions based on a given name.
201  */
202 class 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
253      * @param[in] groups - Groups of dbus objects the action uses
254      * @param[in] zones - Zones the action runs against
255      *
256      * @return Pointer to the action object.
257      */
258     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)
262     {
263         auto it = actions.find(name);
264         if (it != actions.end())
265         {
266             return it->second(jsonObj, groups, zones);
267         }
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 */
285     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>>&)>>
289         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  */
298 template <typename T>
299 class 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
320