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 <nlohmann/json.hpp> 24 #include <phosphor-logging/log.hpp> 25 26 #include <algorithm> 27 #include <format> 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 std::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 _uniqueName(getName() + "-" + std::to_string(_actionCount++)) 124 {} 125 126 /** 127 * @brief Get the groups configured on the action 128 * 129 * @return List of groups 130 */ 131 inline const auto& getGroups() const 132 { 133 return _groups; 134 } 135 136 /** 137 * @brief Set the zones the action is run against 138 * 139 * @param[in] zones - Zones the action runs against 140 * 141 * By default, the zones are set when the action object is created 142 */ 143 virtual void setZones(std::vector<std::reference_wrapper<Zone>>& zones) 144 { 145 _zones = zones; 146 } 147 148 /** 149 * @brief Add a zone to the list of zones the action is run against if its 150 * not already there 151 * 152 * @param[in] zone - Zone to add 153 */ 154 virtual void addZone(Zone& zone) 155 { 156 auto itZone = 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 /** 189 * @brief Returns a unique name for the action. 190 * 191 * @return std::string - The name 192 */ 193 const std::string& getUniqueName() const 194 { 195 return _uniqueName; 196 } 197 198 /** 199 * @brief Set the name of the owning Event. 200 * 201 * Adds it to the unique name in parentheses. If desired, 202 * the action child classes can do something else with it. 203 * 204 * @param[in] name - The event name 205 */ 206 virtual void setEventName(const std::string& name) 207 { 208 if (!name.empty()) 209 { 210 _uniqueName += '(' + name + ')'; 211 } 212 } 213 214 /** 215 * @brief Dump the action as JSON 216 * 217 * For now just dump its group names 218 * 219 * @return json 220 */ 221 json dump() const 222 { 223 json groups = json::array(); 224 std::for_each(_groups.begin(), _groups.end(), 225 [&groups](const auto& group) { 226 groups.push_back(group.getName()); 227 }); 228 json output; 229 output["groups"] = groups; 230 return output; 231 } 232 233 protected: 234 /** 235 * @brief Logs a message to the flight recorder using 236 * the unique name of the action. 237 * 238 * @param[in] message - The message to log 239 */ 240 void record(const std::string& message) const 241 { 242 FlightRecorder::instance().log(getUniqueName(), message); 243 } 244 245 /* Groups configured on the action */ 246 const std::vector<Group> _groups; 247 248 private: 249 /* Zones configured on the action */ 250 std::vector<std::reference_wrapper<Zone>> _zones; 251 252 /* Unique name of the action. 253 * It's just the name plus _actionCount at the time of action creation. */ 254 std::string _uniqueName; 255 256 /* Running count of all actions */ 257 static inline size_t _actionCount = 0; 258 }; 259 260 /** 261 * @class ActionFactory - Factory for actions 262 * 263 * Factory that registers and retrieves actions based on a given name. 264 */ 265 class ActionFactory 266 { 267 public: 268 ActionFactory() = delete; 269 ActionFactory(const ActionFactory&) = delete; 270 ActionFactory(ActionFactory&&) = delete; 271 ActionFactory& operator=(const ActionFactory&) = delete; 272 ActionFactory& operator=(ActionFactory&&) = delete; 273 ~ActionFactory() = default; 274 275 /** 276 * @brief Registers an action 277 * 278 * Registers an action as being available for configuration use. The action 279 * is registered by its name and a function used to create the action 280 * object. An action fails to be registered when another action of the same 281 * name has already been registered. Actions with the same name would cause 282 * undefined behavior, therefore are not allowed. 283 * 284 * Actions are registered prior to starting main(). 285 * 286 * @param[in] name - Name of the action to register 287 * 288 * @return The action was registered, otherwise an exception is thrown. 289 */ 290 template <typename T> 291 static bool regAction(const std::string& name) 292 { 293 auto it = actions.find(name); 294 if (it == actions.end()) 295 { 296 actions[name] = &createAction<T>; 297 } 298 else 299 { 300 log<level::ERR>( 301 std::format("Action '{}' is already registered", name).c_str()); 302 throw std::runtime_error("Actions with the same name found"); 303 } 304 305 return true; 306 } 307 308 /** 309 * @brief Gets a registered action's object 310 * 311 * Gets a registered action's object of a given name from the JSON 312 * configuration data provided. 313 * 314 * @param[in] name - Name of the action to create/get 315 * @param[in] jsonObj - JSON object for the action 316 * @param[in] groups - Groups of dbus objects the action uses 317 * @param[in] zones - Zones the action runs against 318 * 319 * @return Pointer to the action object. 320 */ 321 static std::unique_ptr<ActionBase> 322 getAction(const std::string& name, const json& jsonObj, 323 const std::vector<Group>& groups, 324 std::vector<std::reference_wrapper<Zone>>&& zones) 325 { 326 auto it = actions.find(name); 327 if (it != actions.end()) 328 { 329 return it->second(jsonObj, groups, zones); 330 } 331 else 332 { 333 // Construct list of available actions 334 auto acts = std::accumulate(std::next(actions.begin()), 335 actions.end(), actions.begin()->first, 336 [](auto list, auto act) { 337 return std::move(list) + ", " + act.first; 338 }); 339 log<level::ERR>( 340 std::format("Action '{}' is not registered", name).c_str(), 341 entry("AVAILABLE_ACTIONS=%s", acts.c_str())); 342 throw std::runtime_error("Unsupported action name given"); 343 } 344 } 345 346 private: 347 /* Map to store the available actions and their creation functions */ 348 static inline std::map<std::string, 349 std::function<std::unique_ptr<ActionBase>( 350 const json&, const std::vector<Group>&, 351 std::vector<std::reference_wrapper<Zone>>&)>> 352 actions; 353 }; 354 355 /** 356 * @class ActionRegister - Registers an action class 357 * 358 * Base action registration class that is extended by an action object so 359 * that action is registered and available for use. 360 */ 361 template <typename T> 362 class ActionRegister 363 { 364 public: 365 ActionRegister(const ActionRegister&) = delete; 366 ActionRegister(ActionRegister&&) = delete; 367 ActionRegister& operator=(const ActionRegister&) = delete; 368 ActionRegister& operator=(ActionRegister&&) = delete; 369 virtual ~ActionRegister() = default; 370 ActionRegister() 371 { 372 // Templates instantiated when used, need to assign a value 373 // here so the compiler doesnt remove it 374 registered = true; 375 } 376 377 private: 378 /* Register actions in the factory */ 379 static inline bool registered = ActionFactory::regAction<T>(T::name); 380 }; 381 382 } // namespace phosphor::fan::control::json 383