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 <functional>
28 #include <iterator>
29 #include <map>
30 #include <memory>
31 #include <numeric>
32 
33 namespace phosphor::fan::control::json
34 {
35 
36 using json = nlohmann::json;
37 using namespace phosphor::logging;
38 
39 /**
40  * @class ActionParseError - A parsing error exception
41  *
42  * A parsing error exception that can be used to terminate the application
43  * due to not being able to successfully parse a configured action.
44  */
45 class ActionParseError : public std::runtime_error
46 {
47   public:
48     ActionParseError() = delete;
49     ActionParseError(const ActionParseError&) = delete;
50     ActionParseError(ActionParseError&&) = delete;
51     ActionParseError& operator=(const ActionParseError&) = delete;
52     ActionParseError& operator=(ActionParseError&&) = delete;
53     ~ActionParseError() = default;
54 
55     /**
56      * @brief Action parsing error object
57      *
58      * When parsing an action from the JSON configuration, any critical
59      * attributes that fail to be parsed for an action can throw an
60      * ActionParseError exception to log the parsing failure details and
61      * terminate the application.
62      *
63      * @param[in] name - Name of the action
64      * @param[in] details - Additional details of the parsing error
65      */
66     ActionParseError(const std::string& name, const std::string& details) :
67         std::runtime_error(
68             fmt::format("Failed to parse action {} [{}]", name, details)
69                 .c_str())
70     {}
71 };
72 
73 /**
74  * @brief Function used in creating action objects
75  *
76  * @param[in] jsonObj - JSON object for the action
77  * @param[in] groups - Groups of dbus objects the action uses
78  * @param[in] zones - Zones the action runs against
79  *
80  * Creates an action object given the JSON configuration, list of groups and
81  * sets the zones the action should run against.
82  */
83 template <typename T>
84 std::unique_ptr<T>
85     createAction(const json& jsonObj, const std::vector<Group>& groups,
86                  std::vector<std::reference_wrapper<Zone>>& zones)
87 {
88     // Create the action and set its list of zones
89     auto action = std::make_unique<T>(jsonObj, groups);
90     action->setZones(zones);
91     return action;
92 }
93 
94 /**
95  * @class ActionBase - Base action object
96  *
97  * Base class for fan control's event actions
98  */
99 class ActionBase : public ConfigBase
100 {
101   public:
102     ActionBase() = delete;
103     ActionBase(const ActionBase&) = delete;
104     ActionBase(ActionBase&&) = delete;
105     ActionBase& operator=(const ActionBase&) = delete;
106     ActionBase& operator=(ActionBase&&) = delete;
107     virtual ~ActionBase() = default;
108 
109     /**
110      * @brief Base action object
111      *
112      * @param[in] jsonObj - JSON object containing name and any profiles
113      * @param[in] groups - Groups of dbus objects the action uses
114      *
115      * All actions derived from this base action object must be given a name
116      * that uniquely identifies the action. Optionally, a configured action can
117      * have a list of explicit profiles it should be included in, otherwise
118      * always include the action where no profiles are given.
119      */
120     ActionBase(const json& jsonObj, const std::vector<Group>& groups) :
121         ConfigBase(jsonObj), _groups(groups)
122     {}
123 
124     /**
125      * @brief Get the groups configured on the action
126      *
127      * @return List of groups
128      */
129     inline const auto& getGroups() const
130     {
131         return _groups;
132     }
133 
134     /**
135      * @brief Set the zones the action is run against
136      *
137      * @param[in] zones - Zones the action runs against
138      *
139      * By default, the zones are set when the action object is created
140      */
141     virtual void setZones(std::vector<std::reference_wrapper<Zone>>& zones)
142     {
143         _zones = zones;
144     }
145 
146     /**
147      * @brief Run the action
148      *
149      * Run the action function associated to the derived action object
150      * that performs a specific tasks on a zone configured by a user.
151      *
152      * @param[in] zone - Zone to run the action on
153      */
154     virtual void run(Zone& zone) = 0;
155 
156     /**
157      * @brief Trigger the action to run against all of its zones
158      *
159      * This is the function used by triggers to run the actions against all the
160      * zones that were configured for the action to run against.
161      */
162     void run()
163     {
164         std::for_each(_zones.begin(), _zones.end(),
165                       [this](Zone& zone) { this->run(zone); });
166     }
167 
168   protected:
169     /* Groups configured on the action */
170     const std::vector<Group> _groups;
171 
172   private:
173     /* Zones configured on the action */
174     std::vector<std::reference_wrapper<Zone>> _zones;
175 };
176 
177 /**
178  * @class ActionFactory - Factory for actions
179  *
180  * Factory that registers and retrieves actions based on a given name.
181  */
182 class ActionFactory
183 {
184   public:
185     ActionFactory() = delete;
186     ActionFactory(const ActionFactory&) = delete;
187     ActionFactory(ActionFactory&&) = delete;
188     ActionFactory& operator=(const ActionFactory&) = delete;
189     ActionFactory& operator=(ActionFactory&&) = delete;
190     ~ActionFactory() = default;
191 
192     /**
193      * @brief Registers an action
194      *
195      * Registers an action as being available for configuration use. The action
196      * is registered by its name and a function used to create the action
197      * object. An action fails to be registered when another action of the same
198      * name has already been registered. Actions with the same name would cause
199      * undefined behavior, therefore are not allowed.
200      *
201      * Actions are registered prior to starting main().
202      *
203      * @param[in] name - Name of the action to register
204      *
205      * @return The action was registered, otherwise an exception is thrown.
206      */
207     template <typename T>
208     static bool regAction(const std::string& name)
209     {
210         auto it = actions.find(name);
211         if (it == actions.end())
212         {
213             actions[name] = &createAction<T>;
214         }
215         else
216         {
217             log<level::ERR>(
218                 fmt::format("Action '{}' is already registered", name).c_str());
219             throw std::runtime_error("Actions with the same name found");
220         }
221 
222         return true;
223     }
224 
225     /**
226      * @brief Gets a registered action's object
227      *
228      * Gets a registered action's object of a given name from the JSON
229      * configuration data provided.
230      *
231      * @param[in] name - Name of the action to create/get
232      * @param[in] jsonObj - JSON object for the action
233      * @param[in] groups - Groups of dbus objects the action uses
234      * @param[in] zones - Zones the action runs against
235      *
236      * @return Pointer to the action object.
237      */
238     static std::unique_ptr<ActionBase>
239         getAction(const std::string& name, const json& jsonObj,
240                   const std::vector<Group>& groups,
241                   std::vector<std::reference_wrapper<Zone>>&& zones)
242     {
243         auto it = actions.find(name);
244         if (it != actions.end())
245         {
246             return it->second(jsonObj, groups, zones);
247         }
248         else
249         {
250             // Construct list of available actions
251             auto acts = std::accumulate(
252                 std::next(actions.begin()), actions.end(),
253                 actions.begin()->first, [](auto list, auto act) {
254                     return std::move(list) + ", " + act.first;
255                 });
256             log<level::ERR>(
257                 fmt::format("Action '{}' is not registered", name).c_str(),
258                 entry("AVAILABLE_ACTIONS=%s", acts.c_str()));
259             throw std::runtime_error("Unsupported action name given");
260         }
261     }
262 
263   private:
264     /* Map to store the available actions and their creation functions */
265     static inline std::map<std::string,
266                            std::function<std::unique_ptr<ActionBase>(
267                                const json&, const std::vector<Group>&,
268                                std::vector<std::reference_wrapper<Zone>>&)>>
269         actions;
270 };
271 
272 /**
273  * @class ActionRegister - Registers an action class
274  *
275  * Base action registration class that is extended by an action object so
276  * that action is registered and available for use.
277  */
278 template <typename T>
279 class ActionRegister
280 {
281   public:
282     ActionRegister(const ActionRegister&) = delete;
283     ActionRegister(ActionRegister&&) = delete;
284     ActionRegister& operator=(const ActionRegister&) = delete;
285     ActionRegister& operator=(ActionRegister&&) = delete;
286     virtual ~ActionRegister() = default;
287     ActionRegister()
288     {
289         // Templates instantiated when used, need to assign a value
290         // here so the compiler doesnt remove it
291         registered = true;
292     }
293 
294   private:
295     /* Register actions in the factory */
296     static inline bool registered = ActionFactory::regAction<T>(T::name);
297 };
298 
299 } // namespace phosphor::fan::control::json
300