/** * Copyright © 2022 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include "../utils/flight_recorder.hpp" #include "../zone.hpp" #include "config_base.hpp" #include "group.hpp" #include #include #include #include #include #include #include #include #include namespace phosphor::fan::control::json { using json = nlohmann::json; using namespace phosphor::logging; /** * @class ActionParseError - A parsing error exception * * A parsing error exception that can be used to terminate the application * due to not being able to successfully parse a configured action. */ class ActionParseError : public std::runtime_error { public: ActionParseError() = delete; ActionParseError(const ActionParseError&) = delete; ActionParseError(ActionParseError&&) = delete; ActionParseError& operator=(const ActionParseError&) = delete; ActionParseError& operator=(ActionParseError&&) = delete; ~ActionParseError() = default; /** * @brief Action parsing error object * * When parsing an action from the JSON configuration, any critical * attributes that fail to be parsed for an action can throw an * ActionParseError exception to log the parsing failure details and * terminate the application. * * @param[in] name - Name of the action * @param[in] details - Additional details of the parsing error */ ActionParseError(const std::string& name, const std::string& details) : std::runtime_error( fmt::format("Failed to parse action {} [{}]", name, details) .c_str()) {} }; /** * @brief Function used in creating action objects * * @param[in] jsonObj - JSON object for the action * @param[in] groups - Groups of dbus objects the action uses * @param[in] zones - Zones the action runs against * * Creates an action object given the JSON configuration, list of groups and * sets the zones the action should run against. */ template std::unique_ptr createAction(const json& jsonObj, const std::vector& groups, std::vector>& zones) { // Create the action and set its list of zones auto action = std::make_unique(jsonObj, groups); action->setZones(zones); return action; } /** * @class ActionBase - Base action object * * Base class for fan control's event actions */ class ActionBase : public ConfigBase { public: ActionBase() = delete; ActionBase(const ActionBase&) = delete; ActionBase(ActionBase&&) = delete; ActionBase& operator=(const ActionBase&) = delete; ActionBase& operator=(ActionBase&&) = delete; virtual ~ActionBase() = default; /** * @brief Base action object * * @param[in] jsonObj - JSON object containing name and any profiles * @param[in] groups - Groups of dbus objects the action uses * * All actions derived from this base action object must be given a name * that uniquely identifies the action. Optionally, a configured action can * have a list of explicit profiles it should be included in, otherwise * always include the action where no profiles are given. */ ActionBase(const json& jsonObj, const std::vector& groups) : ConfigBase(jsonObj), _groups(groups), _uniqueName(getName() + "-" + std::to_string(_actionCount++)) {} /** * @brief Get the groups configured on the action * * @return List of groups */ inline const auto& getGroups() const { return _groups; } /** * @brief Set the zones the action is run against * * @param[in] zones - Zones the action runs against * * By default, the zones are set when the action object is created */ virtual void setZones(std::vector>& zones) { _zones = zones; } /** * @brief Add a zone to the list of zones the action is run against if its * not already there * * @param[in] zone - Zone to add */ virtual void addZone(Zone& zone) { auto itZone = std::find_if(_zones.begin(), _zones.end(), [&zone](std::reference_wrapper& z) { return z.get().getName() == zone.getName(); }); if (itZone == _zones.end()) { _zones.emplace_back(std::reference_wrapper(zone)); } } /** * @brief Run the action * * Run the action function associated to the derived action object * that performs a specific tasks on a zone configured by a user. * * @param[in] zone - Zone to run the action on */ virtual void run(Zone& zone) = 0; /** * @brief Trigger the action to run against all of its zones * * This is the function used by triggers to run the actions against all the * zones that were configured for the action to run against. */ void run() { std::for_each(_zones.begin(), _zones.end(), [this](Zone& zone) { this->run(zone); }); } /** * @brief Returns a unique name for the action. * * @return std::string - The name */ const std::string& getUniqueName() const { return _uniqueName; } /** * @brief Set the name of the owning Event. * * Adds it to the unique name in parentheses. If desired, * the action child classes can do something else with it. * * @param[in] name - The event name */ virtual void setEventName(const std::string& name) { if (!name.empty()) { _uniqueName += '(' + name + ')'; } } /** * @brief Dump the action as JSON * * For now just dump its group names * * @return json */ json dump() const { json groups = json::array(); std::for_each(_groups.begin(), _groups.end(), [&groups](const auto& group) { groups.push_back(group.getName()); }); json output; output["groups"] = groups; return output; } protected: /** * @brief Logs a message to the flight recorder using * the unique name of the action. * * @param[in] message - The message to log */ void record(const std::string& message) const { FlightRecorder::instance().log(getUniqueName(), message); } /* Groups configured on the action */ const std::vector _groups; private: /* Zones configured on the action */ std::vector> _zones; /* Unique name of the action. * It's just the name plus _actionCount at the time of action creation. */ std::string _uniqueName; /* Running count of all actions */ static inline size_t _actionCount = 0; }; /** * @class ActionFactory - Factory for actions * * Factory that registers and retrieves actions based on a given name. */ class ActionFactory { public: ActionFactory() = delete; ActionFactory(const ActionFactory&) = delete; ActionFactory(ActionFactory&&) = delete; ActionFactory& operator=(const ActionFactory&) = delete; ActionFactory& operator=(ActionFactory&&) = delete; ~ActionFactory() = default; /** * @brief Registers an action * * Registers an action as being available for configuration use. The action * is registered by its name and a function used to create the action * object. An action fails to be registered when another action of the same * name has already been registered. Actions with the same name would cause * undefined behavior, therefore are not allowed. * * Actions are registered prior to starting main(). * * @param[in] name - Name of the action to register * * @return The action was registered, otherwise an exception is thrown. */ template static bool regAction(const std::string& name) { auto it = actions.find(name); if (it == actions.end()) { actions[name] = &createAction; } else { log( fmt::format("Action '{}' is already registered", name).c_str()); throw std::runtime_error("Actions with the same name found"); } return true; } /** * @brief Gets a registered action's object * * Gets a registered action's object of a given name from the JSON * configuration data provided. * * @param[in] name - Name of the action to create/get * @param[in] jsonObj - JSON object for the action * @param[in] groups - Groups of dbus objects the action uses * @param[in] zones - Zones the action runs against * * @return Pointer to the action object. */ static std::unique_ptr getAction(const std::string& name, const json& jsonObj, const std::vector& groups, std::vector>&& zones) { auto it = actions.find(name); if (it != actions.end()) { return it->second(jsonObj, groups, zones); } else { // Construct list of available actions auto acts = std::accumulate( std::next(actions.begin()), actions.end(), actions.begin()->first, [](auto list, auto act) { return std::move(list) + ", " + act.first; }); log( fmt::format("Action '{}' is not registered", name).c_str(), entry("AVAILABLE_ACTIONS=%s", acts.c_str())); throw std::runtime_error("Unsupported action name given"); } } private: /* Map to store the available actions and their creation functions */ static inline std::map( const json&, const std::vector&, std::vector>&)>> actions; }; /** * @class ActionRegister - Registers an action class * * Base action registration class that is extended by an action object so * that action is registered and available for use. */ template class ActionRegister { public: ActionRegister(const ActionRegister&) = delete; ActionRegister(ActionRegister&&) = delete; ActionRegister& operator=(const ActionRegister&) = delete; ActionRegister& operator=(ActionRegister&&) = delete; virtual ~ActionRegister() = default; ActionRegister() { // Templates instantiated when used, need to assign a value // here so the compiler doesnt remove it registered = true; } private: /* Register actions in the factory */ static inline bool registered = ActionFactory::regAction(T::name); }; } // namespace phosphor::fan::control::json