blob: 2f661817f8779fd27501a3e39505c6e54d8d337c [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 "action.hpp"
#include "event.hpp"
#include "group.hpp"
#include "json_config.hpp"
#include "power_state.hpp"
#include "profile.hpp"
#include "sdbusplus.hpp"
#include "utils/flight_recorder.hpp"
#include "zone.hpp"
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/server/manager.hpp>
#include <sdeventplus/event.hpp>
#include <sdeventplus/source/event.hpp>
#include <sdeventplus/utility/timer.hpp>
#include <chrono>
#include <format>
#include <map>
#include <memory>
#include <optional>
#include <tuple>
#include <utility>
#include <vector>
namespace phosphor::fan::control::json
{
using json = nlohmann::json;
using namespace phosphor::logging;
/* Application name to be appended to the path for loading a JSON config file */
constexpr auto confAppName = "control";
/* Type of timers supported */
enum class TimerType
{
oneshot,
repeating,
};
/**
* Package of data required when a timer expires
* Tuple constructed of:
* std::string = Timer package unique identifier
* std::vector<std::unique_ptr<ActionBase>> = List of pointers to actions
* that run when the timer expires
* const std::vector<Group> = List of groups
* bool = If groups should be preloaded before actions are run
*/
using TimerPkg =
std::tuple<std::string, std::vector<std::unique_ptr<ActionBase>>&,
const std::vector<Group>&, bool>;
/**
* Data associated with a running timer that's used when it expires
* Pair constructed of:
* TimerType = Type of timer to manage expired timer instances
* TimerPkg = Package of data required when the timer expires
*/
using TimerData = std::pair<TimerType, TimerPkg>;
/* Dbus event timer */
using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
/* Dbus signal object */
constexpr auto Path = 0;
constexpr auto Intf = 1;
constexpr auto Prop = 2;
using SignalObject = std::tuple<std::string, std::string, std::string>;
/* Dbus signal actions */
using TriggerActions =
std::vector<std::reference_wrapper<std::unique_ptr<ActionBase>>>;
/**
* Signal handler function that handles parsing a signal's message for a
* particular signal object and stores the results in the manager
*/
using SignalHandler =
std::function<bool(sdbusplus::message_t&, const SignalObject&, Manager&)>;
/**
* Package of data required when a signal is received
* Tuple constructed of:
* SignalHandler = Signal handler function
* SignalObject = Dbus signal object
* TriggerActions = List of actions that are run when the signal is received
*/
using SignalPkg = std::tuple<SignalHandler, SignalObject, TriggerActions>;
/**
* Data associated to a subscribed signal
* Tuple constructed of:
* std::unique_ptr<std::vector<SignalPkg>> =
* Pointer to list of the signal's packages
* std::unique_ptr<sdbusplus::bus::match_t> =
* Pointer to match holding the subscription to a signal
*/
using SignalData = std::tuple<std::unique_ptr<std::vector<SignalPkg>>,
std::unique_ptr<sdbusplus::bus::match_t>>;
/**
* Package of data from a D-Bus call to get managed objects
* Tuple constructed of:
* std::map<Path, // D-Bus Path
* std::map<Intf, // D-Bus Interface
* std::map<Property, // D-Bus Property
* std::variant>>> // Variant value of that property
*/
using Path_v = sdbusplus::message::object_path;
using Intf_v = std::string;
using Prop_v = std::string;
using ManagedObjects =
std::map<Path_v, std::map<Intf_v, std::map<Prop_v, PropertyVariantType>>>;
/**
* Actions to run when a parameter trigger runs.
*/
using ParamTriggerData = std::vector<
std::reference_wrapper<const std::vector<std::unique_ptr<ActionBase>>>>;
/**
* @class Manager - Represents the fan control manager's configuration
*
* A fan control manager configuration is optional, therefore the "manager.json"
* file is also optional. The manager configuration is used to populate
* fan control's manager parameters which are used in how the application
* operates, not in how the fans are controlled.
*
* When no manager configuration exists, the fan control application starts,
* processes any configured events and then begins controlling fans according
* to those events.
*/
class Manager
{
public:
Manager() = delete;
Manager(const Manager&) = delete;
Manager(Manager&&) = delete;
Manager& operator=(const Manager&) = delete;
Manager& operator=(Manager&&) = delete;
~Manager() = default;
/**
* Constructor
* Parses and populates the fan control manager attributes from a json file
*
* @param[in] event - sdeventplus event loop
*/
explicit Manager(const sdeventplus::Event& event);
/**
* @brief Callback function to handle receiving a HUP signal to reload the
* JSON configurations.
*/
void sighupHandler(sdeventplus::source::Signal&,
const struct signalfd_siginfo*);
/**
* @brief Callback function to handle receiving a USR1 signal to dump
* the flight recorder.
*/
void dumpDebugData(sdeventplus::source::Signal&,
const struct signalfd_siginfo*);
/**
* @brief Get the active profiles of the system where an empty list
* represents that only configuration entries without a profile defined will
* be loaded.
*
* @return - The list of active profiles
*/
static const std::vector<std::string>& getActiveProfiles();
/**
* @brief Load the configuration of a given JSON class object based on the
* active profiles
*
* @param[in] isOptional - JSON configuration file is optional or not
* @param[in] args - Arguments to be forwarded to each instance of `T`
* (*Note that a sdbusplus bus object is required as the first argument)
*
* @return Map of configuration entries
* Map of configuration keys to their corresponding configuration object
*/
template <typename T, typename... Args>
static std::map<configKey, std::unique_ptr<T>>
getConfig(bool isOptional, Args&&... args)
{
std::map<configKey, std::unique_ptr<T>> config;
auto confFile = fan::JsonConfig::getConfFile(
confAppName, T::confFileName, isOptional);
if (!confFile.empty())
{
FlightRecorder::instance().log(
"main", std::format("Loading configuration from {}",
confFile.string()));
for (const auto& entry : fan::JsonConfig::load(confFile))
{
if (entry.contains("profiles"))
{
std::vector<std::string> profiles;
for (const auto& profile : entry["profiles"])
{
profiles.emplace_back(
profile.template get<std::string>());
}
// Do not create the object if its profiles are not in the
// list of active profiles
if (!profiles.empty() &&
!std::any_of(profiles.begin(), profiles.end(),
[](const auto& name) {
return std::find(
getActiveProfiles().begin(),
getActiveProfiles().end(),
name) !=
getActiveProfiles().end();
}))
{
continue;
}
}
auto obj =
std::make_unique<T>(entry, std::forward<Args>(args)...);
config.emplace(
std::make_pair(obj->getName(), obj->getProfiles()),
std::move(obj));
}
log<level::INFO>(
std::format("Configuration({}) loaded successfully",
T::confFileName)
.c_str());
FlightRecorder::instance().log(
"main", std::format("Configuration({}) loaded successfully",
T::confFileName));
}
return config;
}
/**
* @brief Check if the given input configuration key matches with another
* configuration key that it's to be included in
*
* @param[in] input - Config key to be included in another config object
* @param[in] comp - Config key of the config object to compare with
*
* @return Whether the configuration object should be included
*/
static bool inConfig(const configKey& input, const configKey& comp);
/**
* @brief Check if the given path and inteface is owned by a dbus service
*
* @param[in] path - Dbus object path
* @param[in] intf - Dbus object interface
*
* @return - Whether the service has an owner for the given object path and
* interface
*/
static bool hasOwner(const std::string& path, const std::string& intf);
/**
* @brief Sets the dbus service owner state for all entries in the _servTree
* cache and removes associated objects from the _objects cache
*
* @param[in] serv - Dbus service name
* @param[in] hasOwner - Dbus service owner state
*/
void setOwner(const std::string& serv, bool hasOwner);
/**
* @brief Sets the dbus service owner state of a given object
*
* @param[in] path - Dbus object path
* @param[in] serv - Dbus service name
* @param[in] intf - Dbus object interface
* @param[in] isOwned - Dbus service owner state
*/
void setOwner(const std::string& path, const std::string& serv,
const std::string& intf, bool isOwned);
/**
* @brief Add a set of services for a path and interface by retrieving all
* the path subtrees to the given depth from root for the interface
*
* @param[in] intf - Interface to add services for
* @param[in] depth - Depth of tree traversal from root path
*
* @throws - DBusMethodError
* Throws a DBusMethodError when the `getSubTree` method call fails
*/
static void addServices(const std::string& intf, int32_t depth);
/**
* @brief Get the service for a given path and interface from cached
* dataset and attempt to add all the services for the given path/interface
* when it's not found
*
* @param[in] path - Path to get service for
* @param[in] intf - Interface to get service for
*
* @return - The now cached service name
*
* @throws - DBusMethodError
* Ripples up a DBusMethodError exception from calling addServices
*/
static const std::string& getService(const std::string& path,
const std::string& intf);
/**
* @brief Get all the object paths for a given service and interface from
* the cached dataset and try to add all the services for the given
* interface when no paths are found and then attempt to get all the object
* paths again
*
* @param[in] serv - Service name to get paths for
* @param[in] intf - Interface to get paths for
*
* @return The cached object paths
*/
std::vector<std::string> getPaths(const std::string& serv,
const std::string& intf);
/**
* @brief Add objects to the cached dataset by first using
* `getManagedObjects` for the same service providing the given path and
* interface or just add the single object of the given path, interface, and
* property if that fails.
*
* @param[in] path - Dbus object's path
* @param[in] intf - Dbus object's interface
* @param[in] prop - Dbus object's property
*
* @throws - DBusMethodError
* Throws a DBusMethodError when the the service is failed to be found or
* when the `getManagedObjects` method call fails
*/
void addObjects(const std::string& path, const std::string& intf,
const std::string& prop)
{
addObjects(path, intf, prop, std::string{});
}
/**
* @copydoc Manager::addObjects()
*
* If the service is known, then it can be used to add all objects
* in that service with the interface passed in to the cache instead of
* having to look it up. This is done so objects can still be
* added even when the D-Bus path passed in doesn't exist so it
* can't be used to get a service name.
*
* @param[in] path - Dbus object's path
* @param[in] intf - Dbus object's interface
* @param[in] prop - Dbus object's property
* @param[in] serviceName - The service of the path/intf/prop if known
*/
void addObjects(const std::string& path, const std::string& intf,
const std::string& prop, const std::string& serviceName);
/**
* @brief Get an object's property value
*
* @param[in] path - Dbus object's path
* @param[in] intf - Dbus object's interface
* @param[in] prop - Dbus object's property
*/
const std::optional<PropertyVariantType>
getProperty(const std::string& path, const std::string& intf,
const std::string& prop);
/**
* @brief Set/update an object's property value
*
* @param[in] path - Dbus object's path
* @param[in] intf - Dbus object's interface
* @param[in] prop - Dbus object's property
* @param[in] value - Dbus object's property value
*/
void setProperty(const std::string& path, const std::string& intf,
const std::string& prop, PropertyVariantType value);
/**
* @brief Remove an object's interface
*
* @param[in] path - Dbus object's path
* @param[in] intf - Dbus object's interface
*/
inline void removeInterface(const std::string& path,
const std::string& intf)
{
auto itPath = _objects.find(path);
if (itPath != std::end(_objects))
{
_objects[path].erase(intf);
}
}
/**
* @brief Get the object's property value as a variant
*
* @param[in] path - Path of the object containing the property
* @param[in] intf - Interface name containing the property
* @param[in] prop - Name of property
*
* @return - The object's property value as a variant
*/
static inline auto getObjValueVariant(const std::string& path,
const std::string& intf,
const std::string& prop)
{
return _objects.at(path).at(intf).at(prop);
};
/**
* @brief Add a dbus timer
*
* @param[in] type - Type of timer
* @param[in] interval - Timer interval in microseconds
* @param[in] pkg - Packaged data for when timer expires
*/
void addTimer(const TimerType type,
const std::chrono::microseconds interval,
std::unique_ptr<TimerPkg> pkg);
/**
* @brief Callback when a timer expires
*
* @param[in] data - Data to be used when the timer expired
*/
void timerExpired(TimerData& data);
/**
* @brief Get the signal data for a given match string
*
* @param[in] sigMatch - Signal match string
*
* @return - Reference to the signal data for the given match string
*/
std::vector<SignalData>& getSignal(const std::string& sigMatch)
{
return _signals[sigMatch];
}
/**
* @brief Handle receiving signals
*
* @param[in] msg - Signal message containing the signal's data
* @param[in] pkgs - Signal packages associated to the signal being handled
*/
void handleSignal(sdbusplus::message_t& msg,
const std::vector<SignalPkg>* pkgs);
/**
* @brief Get the sdbusplus bus object
*/
inline auto& getBus()
{
return _bus;
}
/**
* @brief Is the power state on
*
* @return Current power state of the system
*/
inline bool isPowerOn() const
{
return _powerState->isPowerOn();
}
/**
* @brief Load all the fan control JSON configuration files
*
* This is where all the fan control JSON configuration files are parsed and
* loaded into their associated objects. Anything that needs to be done when
* the Manager object is constructed or handling a SIGHUP to reload the
* configurations needs to be done here.
*/
void load();
/**
* @brief Sets a value in the parameter map.
*
* If it's a std::nullopt, it will be deleted instead.
*
* @param[in] name - The parameter name
* @param[in] value - The parameter value
*/
static void setParameter(const std::string& name,
const std::optional<PropertyVariantType>& value)
{
if (value)
{
auto it = _parameters.find(name);
auto changed = (it == _parameters.end()) ||
((it != _parameters.end()) && it->second != *value);
_parameters[name] = *value;
if (changed)
{
runParameterActions(name);
}
}
else
{
size_t deleted = _parameters.erase(name);
if (deleted)
{
runParameterActions(name);
}
}
}
/**
* @brief Returns a value from the parameter map
*
* @param[in] name - The parameter name
*
* @return The parameter value, or std::nullopt if not found
*/
static std::optional<PropertyVariantType>
getParameter(const std::string& name)
{
auto it = _parameters.find(name);
if (it != _parameters.end())
{
return it->second;
}
return std::nullopt;
}
/**
* @brief Runs the actions registered to a parameter
* trigger with this name.
*
* @param[in] name - The parameter name
*/
static void runParameterActions(const std::string& name);
/**
* @brief Adds a parameter trigger
*
* @param[in] name - The parameter name
* @param[in] actions - The actions to run on the trigger
*/
static void
addParameterTrigger(const std::string& name,
std::vector<std::unique_ptr<ActionBase>>& actions);
/* The name of the dump file */
static const std::string dumpFile;
private:
/**
* @brief Helper to detect when a property's double contains a NaN
* (not-a-number) value.
*
* @param[in] value - The property to test
*/
static bool PropertyContainsNan(const PropertyVariantType& value)
{
return (std::holds_alternative<double>(value) &&
std::isnan(std::get<double>(value)));
}
/**
* @brief Insert managed objects into cache, but filter out properties
* containing unwanted NaN (not-a-number) values and properties that
* are on D-Bus paths that aren't in an existing Group object.
*
* @param[in] ref - The map of ManagedObjects to insert into cache
*/
void insertFilteredObjects(ManagedObjects& ref);
/* The sdbusplus bus object to use */
sdbusplus::bus_t& _bus;
/* The sdeventplus even loop to use */
sdeventplus::Event _event;
/* The sdbusplus manager object to set the ObjectManager interface */
sdbusplus::server::manager_t _mgr;
/* Whether loading the config files is allowed or not */
bool _loadAllowed;
/* The system's power state determination object */
std::unique_ptr<PowerState> _powerState;
/* List of profiles configured */
std::map<configKey, std::unique_ptr<Profile>> _profiles;
/* List of active profiles */
static std::vector<std::string> _activeProfiles;
/* Subtree map of paths to services of interfaces(with ownership state) */
static std::map<
std::string,
std::map<std::string, std::pair<bool, std::vector<std::string>>>>
_servTree;
/* Object map of paths to interfaces of properties and their values */
static std::map<
std::string,
std::map<std::string, std::map<std::string, PropertyVariantType>>>
_objects;
/* List of timers and their data to be processed when expired */
std::vector<std::pair<std::unique_ptr<TimerData>, Timer>> _timers;
/* Map of signal match strings to a list of signal handler data */
std::unordered_map<std::string, std::vector<SignalData>> _signals;
/* List of zones configured */
std::map<configKey, std::unique_ptr<Zone>> _zones;
/* List of events configured */
std::map<configKey, std::unique_ptr<Event>> _events;
/**
* @brief A map of parameter names and values that are something
* other than just D-Bus property values that other actions
* can set and use.
*/
static std::unordered_map<std::string, PropertyVariantType> _parameters;
/**
* @brief Map of parameter names to the actions to run when their
* values change.
*/
static std::unordered_map<std::string, TriggerActions> _parameterTriggers;
/**
* @brief Callback for power state changes
*
* @param[in] powerStateOn - Whether the power state is on or not
*
* Callback function bound to the PowerState object instance to handle each
* time the power state changes.
*/
void powerStateChanged(bool powerStateOn);
/**
* @brief Find the service name for a given path and interface from the
* cached dataset
*
* @param[in] path - Path to get service for
* @param[in] intf - Interface to get service for
*
* @return - The cached service name
*/
static const std::string& findService(const std::string& path,
const std::string& intf);
/**
* @brief Find all the paths for a given service and interface from the
* cached dataset
*
* @param[in] serv - Service name to get paths for
* @param[in] intf - Interface to get paths for
*
* @return - The cached object paths
*/
std::vector<std::string> findPaths(const std::string& serv,
const std::string& intf);
/**
* @brief Parse and set the configured profiles from the profiles JSON file
*
* Retrieves the optional profiles JSON configuration file, parses it, and
* creates a list of configured profiles available to the other
* configuration files. These profiles can be used to remove or include
* entries within the other configuration files.
*/
void setProfiles();
/**
* @brief Dump the _objects, _servTree, and _parameters maps to JSON
*
* @param[out] data - The JSON that will be filled in
*/
void dumpCache(json& data);
/**
* @brief Add a list of groups to the cache dataset.
*
* @param[in] groups - The groups to add
*/
void addGroups(const std::vector<Group>& groups);
};
} // namespace phosphor::fan::control::json