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