blob: 40547b95f9291fdfd13ad3bc936a40861c78856a [file] [log] [blame]
Matthew Barth0c4b1572020-10-22 14:39:46 -05001/**
Mike Capps59af8ca2021-10-07 15:42:28 -04002 * Copyright © 2022 IBM Corporation
Matthew Barth0c4b1572020-10-22 14:39:46 -05003 *
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
Matt Spinlerc9d49a62021-10-04 16:12:15 -050018#include "../utils/flight_recorder.hpp"
Matthew Barthd9cb63b2021-03-24 14:36:49 -050019#include "../zone.hpp"
Matthew Barthbfd7e1b2021-02-04 14:25:47 -060020#include "config_base.hpp"
Matthew Barth12cb1252021-03-08 16:47:30 -060021#include "group.hpp"
Matthew Barth0c4b1572020-10-22 14:39:46 -050022
23#include <fmt/format.h>
24
25#include <nlohmann/json.hpp>
26#include <phosphor-logging/log.hpp>
27
Matthew Barthb5811a62021-06-30 14:32:21 -050028#include <algorithm>
Matthew Barth0c4b1572020-10-22 14:39:46 -050029#include <functional>
30#include <iterator>
31#include <map>
32#include <memory>
33#include <numeric>
34
35namespace phosphor::fan::control::json
36{
37
38using json = nlohmann::json;
39using namespace phosphor::logging;
40
41/**
Matthew Barthbfd7e1b2021-02-04 14:25:47 -060042 * @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 */
47class 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/**
Matthew Barth0c4b1572020-10-22 14:39:46 -050076 * @brief Function used in creating action objects
77 *
78 * @param[in] jsonObj - JSON object for the action
Matthew Barth19c77492021-04-08 10:06:06 -050079 * @param[in] groups - Groups of dbus objects the action uses
80 * @param[in] zones - Zones the action runs against
Matthew Barth0c4b1572020-10-22 14:39:46 -050081 *
Matthew Barth19c77492021-04-08 10:06:06 -050082 * Creates an action object given the JSON configuration, list of groups and
83 * sets the zones the action should run against.
Matthew Barth0c4b1572020-10-22 14:39:46 -050084 */
85template <typename T>
Matthew Barth19c77492021-04-08 10:06:06 -050086std::unique_ptr<T>
87 createAction(const json& jsonObj, const std::vector<Group>& groups,
88 std::vector<std::reference_wrapper<Zone>>& zones)
Matthew Barth0c4b1572020-10-22 14:39:46 -050089{
Matthew Barth19c77492021-04-08 10:06:06 -050090 // 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;
Matthew Barth0c4b1572020-10-22 14:39:46 -050094}
95
96/**
97 * @class ActionBase - Base action object
98 *
99 * Base class for fan control's event actions
100 */
Matthew Barthbfd7e1b2021-02-04 14:25:47 -0600101class ActionBase : public ConfigBase
Matthew Barth0c4b1572020-10-22 14:39:46 -0500102{
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;
Matthew Barth41a34082021-01-27 15:31:48 -0600110
111 /**
112 * @brief Base action object
113 *
Matthew Barth19c77492021-04-08 10:06:06 -0500114 * @param[in] jsonObj - JSON object containing name and any profiles
115 * @param[in] groups - Groups of dbus objects the action uses
116 *
Matthew Barth41a34082021-01-27 15:31:48 -0600117 * All actions derived from this base action object must be given a name
Matthew Barthbfd7e1b2021-02-04 14:25:47 -0600118 * 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.
Matthew Barth41a34082021-01-27 15:31:48 -0600121 */
Matthew Barth19c77492021-04-08 10:06:06 -0500122 ActionBase(const json& jsonObj, const std::vector<Group>& groups) :
Matt Spinlerc9d49a62021-10-04 16:12:15 -0500123 ConfigBase(jsonObj), _groups(groups),
124 _uniqueName(getName() + "-" + std::to_string(_actionCount++))
Matthew Barth0c4b1572020-10-22 14:39:46 -0500125 {}
126
127 /**
Matthew Bartheebde062021-04-14 12:48:52 -0500128 * @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 /**
Matthew Barth19c77492021-04-08 10:06:06 -0500138 * @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 /**
Matthew Barthb5811a62021-06-30 14:32:21 -0500150 * @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 /**
Matthew Barth41a34082021-01-27 15:31:48 -0600169 * @brief Run the action
Matthew Barth0c4b1572020-10-22 14:39:46 -0500170 *
Matthew Barth41a34082021-01-27 15:31:48 -0600171 * Run the action function associated to the derived action object
Matthew Barth6d2476c2021-04-08 10:48:57 -0500172 * that performs a specific tasks on a zone configured by a user.
Matthew Barth0c4b1572020-10-22 14:39:46 -0500173 *
Matthew Barth41a34082021-01-27 15:31:48 -0600174 * @param[in] zone - Zone to run the action on
Matthew Barth0c4b1572020-10-22 14:39:46 -0500175 */
Matthew Barth6d2476c2021-04-08 10:48:57 -0500176 virtual void run(Zone& zone) = 0;
Matthew Barth19c77492021-04-08 10:06:06 -0500177
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 {
Matthew Barth19c77492021-04-08 10:06:06 -0500186 std::for_each(_zones.begin(), _zones.end(),
Matthew Barth6d2476c2021-04-08 10:48:57 -0500187 [this](Zone& zone) { this->run(zone); });
Matthew Barth19c77492021-04-08 10:06:06 -0500188 }
189
Matt Spinlerc9d49a62021-10-04 16:12:15 -0500190 /**
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
Matt Spinlere6fc2102022-04-07 14:31:29 -0500200 /**
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
Matthew Barth19c77492021-04-08 10:06:06 -0500216 protected:
Matt Spinlerc9d49a62021-10-04 16:12:15 -0500217 /**
218 * @brief Logs a message to the flight recorder using
219 * the unique name of the action.
220 *
221 * @param[in] message - The message to log
222 */
223 void record(const std::string& message) const
224 {
225 FlightRecorder::instance().log(getUniqueName(), message);
226 }
227
Matthew Barth19c77492021-04-08 10:06:06 -0500228 /* Groups configured on the action */
229 const std::vector<Group> _groups;
230
231 private:
232 /* Zones configured on the action */
233 std::vector<std::reference_wrapper<Zone>> _zones;
Matt Spinlerc9d49a62021-10-04 16:12:15 -0500234
235 /* Unique name of the action.
236 * It's just the name plus _actionCount at the time of action creation. */
Matt Spinlere6fc2102022-04-07 14:31:29 -0500237 std::string _uniqueName;
Matt Spinlerc9d49a62021-10-04 16:12:15 -0500238
239 /* Running count of all actions */
240 static inline size_t _actionCount = 0;
Matthew Barth0c4b1572020-10-22 14:39:46 -0500241};
242
243/**
244 * @class ActionFactory - Factory for actions
245 *
246 * Factory that registers and retrieves actions based on a given name.
247 */
248class ActionFactory
249{
250 public:
251 ActionFactory() = delete;
252 ActionFactory(const ActionFactory&) = delete;
253 ActionFactory(ActionFactory&&) = delete;
254 ActionFactory& operator=(const ActionFactory&) = delete;
255 ActionFactory& operator=(ActionFactory&&) = delete;
256 ~ActionFactory() = default;
257
258 /**
259 * @brief Registers an action
260 *
261 * Registers an action as being available for configuration use. The action
262 * is registered by its name and a function used to create the action
263 * object. An action fails to be registered when another action of the same
264 * name has already been registered. Actions with the same name would cause
265 * undefined behavior, therefore are not allowed.
266 *
267 * Actions are registered prior to starting main().
268 *
269 * @param[in] name - Name of the action to register
270 *
271 * @return The action was registered, otherwise an exception is thrown.
272 */
273 template <typename T>
274 static bool regAction(const std::string& name)
275 {
276 auto it = actions.find(name);
277 if (it == actions.end())
278 {
279 actions[name] = &createAction<T>;
280 }
281 else
282 {
283 log<level::ERR>(
284 fmt::format("Action '{}' is already registered", name).c_str());
285 throw std::runtime_error("Actions with the same name found");
286 }
287
288 return true;
289 }
290
291 /**
292 * @brief Gets a registered action's object
293 *
294 * Gets a registered action's object of a given name from the JSON
295 * configuration data provided.
296 *
297 * @param[in] name - Name of the action to create/get
298 * @param[in] jsonObj - JSON object for the action
Matthew Barth19c77492021-04-08 10:06:06 -0500299 * @param[in] groups - Groups of dbus objects the action uses
300 * @param[in] zones - Zones the action runs against
Matthew Barth0c4b1572020-10-22 14:39:46 -0500301 *
302 * @return Pointer to the action object.
303 */
Matthew Barth46b34482021-04-06 11:27:23 -0500304 static std::unique_ptr<ActionBase>
305 getAction(const std::string& name, const json& jsonObj,
306 const std::vector<Group>& groups,
307 std::vector<std::reference_wrapper<Zone>>&& zones)
Matthew Barth0c4b1572020-10-22 14:39:46 -0500308 {
309 auto it = actions.find(name);
310 if (it != actions.end())
311 {
Matthew Barth19c77492021-04-08 10:06:06 -0500312 return it->second(jsonObj, groups, zones);
Matthew Barth0c4b1572020-10-22 14:39:46 -0500313 }
314 else
315 {
316 // Construct list of available actions
317 auto acts = std::accumulate(
318 std::next(actions.begin()), actions.end(),
319 actions.begin()->first, [](auto list, auto act) {
320 return std::move(list) + ", " + act.first;
321 });
322 log<level::ERR>(
323 fmt::format("Action '{}' is not registered", name).c_str(),
324 entry("AVAILABLE_ACTIONS=%s", acts.c_str()));
325 throw std::runtime_error("Unsupported action name given");
326 }
327 }
328
329 private:
330 /* Map to store the available actions and their creation functions */
Matthew Barth19c77492021-04-08 10:06:06 -0500331 static inline std::map<std::string,
332 std::function<std::unique_ptr<ActionBase>(
333 const json&, const std::vector<Group>&,
334 std::vector<std::reference_wrapper<Zone>>&)>>
Matthew Barth0c4b1572020-10-22 14:39:46 -0500335 actions;
336};
337
338/**
339 * @class ActionRegister - Registers an action class
340 *
341 * Base action registration class that is extended by an action object so
342 * that action is registered and available for use.
343 */
344template <typename T>
345class ActionRegister
346{
347 public:
348 ActionRegister(const ActionRegister&) = delete;
349 ActionRegister(ActionRegister&&) = delete;
350 ActionRegister& operator=(const ActionRegister&) = delete;
351 ActionRegister& operator=(ActionRegister&&) = delete;
352 virtual ~ActionRegister() = default;
353 ActionRegister()
354 {
355 // Templates instantiated when used, need to assign a value
356 // here so the compiler doesnt remove it
357 registered = true;
358 }
359
360 private:
361 /* Register actions in the factory */
362 static inline bool registered = ActionFactory::regAction<T>(T::name);
363};
364
365} // namespace phosphor::fan::control::json