| /** |
| * 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 <nlohmann/json.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <algorithm> |
| #include <format> |
| #include <functional> |
| #include <iterator> |
| #include <map> |
| #include <memory> |
| #include <numeric> |
| |
| 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( |
| std::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 <typename T> |
| std::unique_ptr<T> |
| createAction(const json& jsonObj, const std::vector<Group>& groups, |
| std::vector<std::reference_wrapper<Zone>>& zones) |
| { |
| // Create the action and set its list of zones |
| auto action = std::make_unique<T>(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<Group>& 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<std::reference_wrapper<Zone>>& 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<Zone>& z) { |
| return z.get().getName() == zone.getName(); |
| }); |
| if (itZone == _zones.end()) |
| { |
| _zones.emplace_back(std::reference_wrapper<Zone>(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<Group> _groups; |
| |
| private: |
| /* Zones configured on the action */ |
| std::vector<std::reference_wrapper<Zone>> _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 <typename T> |
| static bool regAction(const std::string& name) |
| { |
| auto it = actions.find(name); |
| if (it == actions.end()) |
| { |
| actions[name] = &createAction<T>; |
| } |
| else |
| { |
| log<level::ERR>( |
| std::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<ActionBase> |
| getAction(const std::string& name, const json& jsonObj, |
| const std::vector<Group>& groups, |
| std::vector<std::reference_wrapper<Zone>>&& 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<level::ERR>( |
| std::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<std::string, |
| std::function<std::unique_ptr<ActionBase>( |
| const json&, const std::vector<Group>&, |
| std::vector<std::reference_wrapper<Zone>>&)>> |
| 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 <typename T> |
| 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>(T::name); |
| }; |
| |
| } // namespace phosphor::fan::control::json |