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