blob: 93cd9e20e1f71048e1a8297f4607896eab02dec9 [file] [log] [blame]
/**
* 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