blob: 6ae51c981ddb3fe31256825b7a94fce370203442 [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.
*/
#include "config.h"
#include "manager.hpp"
#include "action.hpp"
#include "dbus_paths.hpp"
#include "event.hpp"
#include "fan.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 <systemd/sd-bus.h>
#include <nlohmann/json.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/server/manager.hpp>
#include <sdeventplus/event.hpp>
#include <sdeventplus/utility/timer.hpp>
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <functional>
#include <map>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
namespace phosphor::fan::control::json
{
using json = nlohmann::json;
std::vector<std::string> Manager::_activeProfiles;
std::map<std::string,
std::map<std::string, std::pair<bool, std::vector<std::string>>>>
Manager::_servTree;
std::map<std::string,
std::map<std::string, std::map<std::string, PropertyVariantType>>>
Manager::_objects;
std::unordered_map<std::string, PropertyVariantType> Manager::_parameters;
std::unordered_map<std::string, TriggerActions> Manager::_parameterTriggers;
const std::string Manager::dumpFile = "/tmp/fan_control_dump.json";
Manager::Manager(const sdeventplus::Event& event) :
_bus(util::SDBusPlus::getBus()), _event(event),
_mgr(util::SDBusPlus::getBus(), CONTROL_OBJPATH), _loadAllowed(true),
_powerState(std::make_unique<PGoodState>(
util::SDBusPlus::getBus(),
std::bind(std::mem_fn(&Manager::powerStateChanged), this,
std::placeholders::_1)))
{}
void Manager::sighupHandler(sdeventplus::source::Signal&,
const struct signalfd_siginfo*)
{
FlightRecorder::instance().log("main", "SIGHUP received");
// Save current set of available and active profiles
std::map<configKey, std::unique_ptr<Profile>> profiles;
profiles.swap(_profiles);
std::vector<std::string> activeProfiles;
activeProfiles.swap(_activeProfiles);
try
{
_loadAllowed = true;
load();
}
catch (const std::runtime_error& re)
{
// Restore saved available and active profiles
_loadAllowed = false;
_profiles.swap(profiles);
_activeProfiles.swap(activeProfiles);
log<level::ERR>("Error reloading configs, no changes made",
entry("LOAD_ERROR=%s", re.what()));
FlightRecorder::instance().log(
"main", std::format("Error reloading configs, no changes made: {}",
re.what()));
}
}
void Manager::dumpDebugData(sdeventplus::source::Signal&,
const struct signalfd_siginfo*)
{
json data;
FlightRecorder::instance().dump(data);
dumpCache(data);
std::for_each(_zones.begin(), _zones.end(), [&data](const auto& zone) {
data["zones"][zone.second->getName()] = zone.second->dump();
});
std::ofstream file{Manager::dumpFile};
if (!file)
{
log<level::ERR>("Could not open file for fan dump");
return;
}
file << std::setw(4) << data;
}
void Manager::dumpCache(json& data)
{
auto& objects = data["objects"];
for (const auto& [path, interfaces] : _objects)
{
auto& interfaceJSON = objects[path];
for (const auto& [interface, properties] : interfaces)
{
auto& propertyJSON = interfaceJSON[interface];
for (const auto& [propName, propValue] : properties)
{
std::visit(
[&obj = propertyJSON[propName]](auto&& val) { obj = val; },
propValue);
}
}
}
auto& parameters = data["parameters"];
for (const auto& [name, value] : _parameters)
{
std::visit([&obj = parameters[name]](auto&& val) { obj = val; }, value);
}
std::for_each(_events.begin(), _events.end(), [&data](const auto& event) {
data["events"][event.second->getName()] = event.second->dump();
});
data["services"] = _servTree;
}
void Manager::load()
{
if (_loadAllowed)
{
// Load the available profiles and which are active
setProfiles();
// Load the zone configurations
auto zones = getConfig<Zone>(false, _event, this);
// Load the fan configurations and move each fan into its zone
auto fans = getConfig<Fan>(false);
for (auto& fan : fans)
{
configKey fanProfile =
std::make_pair(fan.second->getZone(), fan.first.second);
auto itZone = std::find_if(
zones.begin(), zones.end(), [&fanProfile](const auto& zone) {
return Manager::inConfig(fanProfile, zone.first);
});
if (itZone != zones.end())
{
if (itZone->second->getTarget() != fan.second->getTarget() &&
fan.second->getTarget() != 0)
{
// Update zone target to current target of the fan in the
// zone
itZone->second->setTarget(fan.second->getTarget());
}
itZone->second->addFan(std::move(fan.second));
}
}
// Save all currently available groups, if any, then clear for reloading
auto groups = std::move(Event::getAllGroups(false));
Event::clearAllGroups();
std::map<configKey, std::unique_ptr<Event>> events;
try
{
// Load any events configured, including all the groups
events = getConfig<Event>(true, this, zones);
}
catch (const std::runtime_error& re)
{
// Restore saved set of all available groups for current events
Event::setAllGroups(std::move(groups));
throw re;
}
// Enable zones
_zones = std::move(zones);
std::for_each(_zones.begin(), _zones.end(),
[](const auto& entry) { entry.second->enable(); });
// Clear current timers and signal subscriptions before enabling events
// To save reloading services and/or objects into cache, do not clear
// cache
_timers.clear();
_signals.clear();
// Enable events
_events = std::move(events);
FlightRecorder::instance().log("main", "Enabling events");
std::for_each(_events.begin(), _events.end(),
[](const auto& entry) { entry.second->enable(); });
FlightRecorder::instance().log("main", "Done enabling events");
_loadAllowed = false;
}
}
void Manager::powerStateChanged(bool powerStateOn)
{
if (powerStateOn)
{
FlightRecorder::instance().log("power", "Power on");
if (_zones.empty())
{
throw std::runtime_error("No configured zones found at poweron");
}
std::for_each(_zones.begin(), _zones.end(), [](const auto& entry) {
entry.second->setTarget(entry.second->getPoweronTarget());
});
// Tell events to run their power on triggers
std::for_each(_events.begin(), _events.end(),
[](const auto& entry) { entry.second->powerOn(); });
}
else
{
FlightRecorder::instance().log("power", "Power off");
// Tell events to run their power off triggers
std::for_each(_events.begin(), _events.end(),
[](const auto& entry) { entry.second->powerOff(); });
}
}
const std::vector<std::string>& Manager::getActiveProfiles()
{
return _activeProfiles;
}
bool Manager::inConfig(const configKey& input, const configKey& comp)
{
// Config names dont match, do not include in config
if (input.first != comp.first)
{
return false;
}
// No profiles specified by input config, can be used in any config
if (input.second.empty())
{
return true;
}
else
{
// Profiles must have one match in the other's profiles(and they must be
// an active profile) to be used in the config
return std::any_of(
input.second.begin(), input.second.end(),
[&comp](const auto& lProfile) {
return std::any_of(
comp.second.begin(), comp.second.end(),
[&lProfile](const auto& rProfile) {
if (lProfile != rProfile)
{
return false;
}
auto activeProfs = getActiveProfiles();
return std::find(activeProfs.begin(), activeProfs.end(),
lProfile) != activeProfs.end();
});
});
}
}
bool Manager::hasOwner(const std::string& path, const std::string& intf)
{
auto itServ = _servTree.find(path);
if (itServ == _servTree.end())
{
// Path not found in cache, therefore owner missing
return false;
}
for (const auto& service : itServ->second)
{
auto itIntf = std::find_if(
service.second.second.begin(), service.second.second.end(),
[&intf](const auto& interface) { return intf == interface; });
if (itIntf != std::end(service.second.second))
{
// Service found, return owner state
return service.second.first;
}
}
// Interface not found in cache, therefore owner missing
return false;
}
void Manager::setOwner(const std::string& serv, bool hasOwner)
{
// Update owner state on all entries of `serv`
for (auto& itPath : _servTree)
{
auto itServ = itPath.second.find(serv);
if (itServ != itPath.second.end())
{
itServ->second.first = hasOwner;
// Remove associated interfaces from object cache when service no
// longer has an owner
if (!hasOwner && _objects.find(itPath.first) != _objects.end())
{
for (auto& intf : itServ->second.second)
{
_objects[itPath.first].erase(intf);
}
}
}
}
}
void Manager::setOwner(const std::string& path, const std::string& serv,
const std::string& intf, bool isOwned)
{
// Set owner state for specific object given
auto& ownIntf = _servTree[path][serv];
ownIntf.first = isOwned;
auto itIntf = std::find_if(
ownIntf.second.begin(), ownIntf.second.end(),
[&intf](const auto& interface) { return intf == interface; });
if (itIntf == std::end(ownIntf.second))
{
ownIntf.second.emplace_back(intf);
}
// Update owner state on all entries of the same `serv` & `intf`
for (auto& itPath : _servTree)
{
if (itPath.first == path)
{
// Already set/updated owner on this path for `serv` & `intf`
continue;
}
for (auto& itServ : itPath.second)
{
if (itServ.first != serv)
{
continue;
}
auto itIntf = std::find_if(
itServ.second.second.begin(), itServ.second.second.end(),
[&intf](const auto& interface) { return intf == interface; });
if (itIntf != std::end(itServ.second.second))
{
itServ.second.first = isOwned;
}
}
}
}
const std::string& Manager::findService(const std::string& path,
const std::string& intf)
{
static const std::string empty = "";
auto itServ = _servTree.find(path);
if (itServ != _servTree.end())
{
for (const auto& service : itServ->second)
{
auto itIntf = std::find_if(
service.second.second.begin(), service.second.second.end(),
[&intf](const auto& interface) { return intf == interface; });
if (itIntf != std::end(service.second.second))
{
// Service found, return service name
return service.first;
}
}
}
return empty;
}
void Manager::addServices(const std::string& intf, int32_t depth)
{
// Get all subtree objects for the given interface
auto objects = util::SDBusPlus::getSubTreeRaw(util::SDBusPlus::getBus(),
"/", intf, depth);
// Add what's returned to the cache of path->services
for (auto& itPath : objects)
{
auto pathIter = _servTree.find(itPath.first);
if (pathIter != _servTree.end())
{
// Path found in cache
for (auto& itServ : itPath.second)
{
auto servIter = pathIter->second.find(itServ.first);
if (servIter != pathIter->second.end())
{
if (std::find(servIter->second.second.begin(),
servIter->second.second.end(), intf) ==
servIter->second.second.end())
{
// Add interface to cache
servIter->second.second.emplace_back(intf);
}
}
else
{
// Service not found in cache
auto intfs = {intf};
pathIter->second[itServ.first] =
std::make_pair(true, intfs);
}
}
}
else
{
// Path not found in cache
auto intfs = {intf};
for (const auto& [servName, servIntfs] : itPath.second)
{
_servTree[itPath.first][servName] = std::make_pair(true, intfs);
}
}
}
}
const std::string& Manager::getService(const std::string& path,
const std::string& intf)
{
// Retrieve service from cache
const auto& serviceName = findService(path, intf);
if (serviceName.empty())
{
addServices(intf, 0);
return findService(path, intf);
}
return serviceName;
}
std::vector<std::string>
Manager::findPaths(const std::string& serv, const std::string& intf)
{
std::vector<std::string> paths;
for (const auto& path : _servTree)
{
auto itServ = path.second.find(serv);
if (itServ != path.second.end())
{
if (std::find(itServ->second.second.begin(),
itServ->second.second.end(), intf) !=
itServ->second.second.end())
{
if (std::find(paths.begin(), paths.end(), path.first) ==
paths.end())
{
paths.push_back(path.first);
}
}
}
}
return paths;
}
std::vector<std::string> Manager::getPaths(const std::string& serv,
const std::string& intf)
{
auto paths = findPaths(serv, intf);
if (paths.empty())
{
addServices(intf, 0);
return findPaths(serv, intf);
}
return paths;
}
void Manager::insertFilteredObjects(ManagedObjects& ref)
{
// Filter out objects that aren't part of a group
const auto& allGroupMembers = Group::getAllMembers();
auto it = ref.begin();
while (it != ref.end())
{
if (allGroupMembers.find(it->first) == allGroupMembers.end())
{
it = ref.erase(it);
}
else
{
it++;
}
}
for (auto& [path, pathMap] : ref)
{
for (auto& [intf, intfMap] : pathMap)
{
// for each property on this path+interface
for (auto& [prop, value] : intfMap)
{
setProperty(path, intf, prop, value);
}
}
}
}
void Manager::addObjects(const std::string& path, const std::string& intf,
const std::string& prop,
const std::string& serviceName)
{
auto service = serviceName;
if (service.empty())
{
service = getService(path, intf);
if (service.empty())
{
// Log service not found for object
log<level::DEBUG>(
std::format(
"Unable to get service name for path {}, interface {}",
path, intf)
.c_str());
return;
}
}
else
{
// The service is known, so the service cache can be
// populated even if the path itself isn't present.
const auto& s = findService(path, intf);
if (s.empty())
{
addServices(intf, 0);
}
}
auto objMgrPaths = getPaths(service, "org.freedesktop.DBus.ObjectManager");
bool useManagedObj = false;
if (!objMgrPaths.empty())
{
for (const auto& objMgrPath : objMgrPaths)
{
// Get all managed objects of service
auto objects =
util::SDBusPlus::getManagedObjects<PropertyVariantType>(
_bus, service, objMgrPath);
if (objects.size() > 0)
{
useManagedObj = true;
}
// insert all objects that are in groups but remove any NaN values
insertFilteredObjects(objects);
}
}
if (!useManagedObj)
{
// No object manager interface provided by service?
// Or no object is managed?
// Attempt to retrieve property directly
try
{
auto value =
util::SDBusPlus::getPropertyVariant<PropertyVariantType>(
_bus, service, path, intf, prop);
setProperty(path, intf, prop, value);
}
catch (const std::exception& e)
{}
return;
}
}
const std::optional<PropertyVariantType> Manager::getProperty(
const std::string& path, const std::string& intf, const std::string& prop)
{
// TODO Objects hosted by fan control (i.e. ThermalMode) are required to
// update the cache upon being set/updated
auto itPath = _objects.find(path);
if (itPath != _objects.end())
{
auto itIntf = itPath->second.find(intf);
if (itIntf != itPath->second.end())
{
auto itProp = itIntf->second.find(prop);
if (itProp != itIntf->second.end())
{
return itProp->second;
}
}
}
return std::nullopt;
}
void Manager::setProperty(const std::string& path, const std::string& intf,
const std::string& prop, PropertyVariantType value)
{
// filter NaNs out of the cache
if (PropertyContainsNan(value))
{
// dont use operator [] if paths dont exist
if (_objects.find(path) != _objects.end() &&
_objects[path].find(intf) != _objects[path].end())
{
_objects[path][intf].erase(prop);
}
}
else
{
_objects[path][intf][prop] = std::move(value);
}
}
void Manager::addTimer(const TimerType type,
const std::chrono::microseconds interval,
std::unique_ptr<TimerPkg> pkg)
{
auto dataPtr =
std::make_unique<TimerData>(std::make_pair(type, std::move(*pkg)));
Timer timer(_event,
std::bind(&Manager::timerExpired, this, std::ref(*dataPtr)));
if (type == TimerType::repeating)
{
timer.restart(interval);
}
else if (type == TimerType::oneshot)
{
timer.restartOnce(interval);
}
else
{
throw std::invalid_argument("Invalid Timer Type");
}
_timers.emplace_back(std::move(dataPtr), std::move(timer));
}
void Manager::addGroups(const std::vector<Group>& groups)
{
std::string lastServ;
std::vector<std::string> objMgrPaths;
std::set<std::string> services;
for (const auto& group : groups)
{
for (const auto& member : group.getMembers())
{
try
{
auto service = group.getService();
if (service.empty())
{
service = getService(member, group.getInterface());
}
if (!service.empty())
{
if (lastServ != service)
{
objMgrPaths = getPaths(
service, "org.freedesktop.DBus.ObjectManager");
lastServ = service;
}
// Look for the ObjectManager as an ancestor from the
// member.
auto hasObjMgr = std::any_of(
objMgrPaths.begin(), objMgrPaths.end(),
[&member](const auto& path) {
return member.find(path) != std::string::npos;
});
if (!hasObjMgr)
{
// No object manager interface provided for group member
// Attempt to retrieve group member property directly
try
{
auto value = util::SDBusPlus::getPropertyVariant<
PropertyVariantType>(_bus, service, member,
group.getInterface(),
group.getProperty());
setProperty(member, group.getInterface(),
group.getProperty(), value);
}
catch (const std::exception& e)
{}
continue;
}
if (services.find(service) == services.end())
{
services.insert(service);
for (const auto& objMgrPath : objMgrPaths)
{
// Get all managed objects from the service
auto objects = util::SDBusPlus::getManagedObjects<
PropertyVariantType>(_bus, service, objMgrPath);
// Insert objects into cache
insertFilteredObjects(objects);
}
}
}
}
catch (const util::DBusError&)
{
// No service or property found for group member with the
// group's configured interface
continue;
}
}
}
}
void Manager::timerExpired(TimerData& data)
{
if (std::get<bool>(data.second))
{
addGroups(std::get<const std::vector<Group>&>(data.second));
}
auto& actions =
std::get<std::vector<std::unique_ptr<ActionBase>>&>(data.second);
// Perform the actions in the timer data
std::for_each(actions.begin(), actions.end(),
[](auto& action) { action->run(); });
// Remove oneshot timers after they expired
if (data.first == TimerType::oneshot)
{
auto itTimer = std::find_if(
_timers.begin(), _timers.end(), [&data](const auto& timer) {
return (data.first == timer.first->first &&
(std::get<std::string>(data.second) ==
std::get<std::string>(timer.first->second)));
});
if (itTimer != std::end(_timers))
{
_timers.erase(itTimer);
}
}
}
void Manager::handleSignal(sdbusplus::message_t& msg,
const std::vector<SignalPkg>* pkgs)
{
for (auto& pkg : *pkgs)
{
// Handle the signal callback and only run the actions if the handler
// updated the cache for the given SignalObject
if (std::get<SignalHandler>(
pkg)(msg, std::get<SignalObject>(pkg), *this))
{
// Perform the actions in the handler package
auto& actions = std::get<TriggerActions>(pkg);
std::for_each(actions.begin(), actions.end(), [](auto& action) {
if (action.get())
{
action.get()->run();
}
});
}
// Only rewind message when not last package
if (&pkg != &pkgs->back())
{
sd_bus_message_rewind(msg.get(), true);
}
}
}
void Manager::setProfiles()
{
// Profiles JSON config file is optional
auto confFile =
fan::JsonConfig::getConfFile(confAppName, Profile::confFileName, true);
_profiles.clear();
if (!confFile.empty())
{
for (const auto& entry : fan::JsonConfig::load(confFile))
{
auto obj = std::make_unique<Profile>(entry);
_profiles.emplace(
std::make_pair(obj->getName(), obj->getProfiles()),
std::move(obj));
}
}
// Ensure all configurations use the same set of active profiles
// (In case a profile's active state changes during configuration)
_activeProfiles.clear();
for (const auto& profile : _profiles)
{
if (profile.second->isActive())
{
_activeProfiles.emplace_back(profile.first.first);
}
}
}
void Manager::addParameterTrigger(
const std::string& name, std::vector<std::unique_ptr<ActionBase>>& actions)
{
auto it = _parameterTriggers.find(name);
if (it != _parameterTriggers.end())
{
std::for_each(actions.begin(), actions.end(),
[&actList = it->second](auto& action) {
actList.emplace_back(std::ref(action));
});
}
else
{
TriggerActions triggerActions;
std::for_each(actions.begin(), actions.end(),
[&triggerActions](auto& action) {
triggerActions.emplace_back(std::ref(action));
});
_parameterTriggers[name] = std::move(triggerActions);
}
}
void Manager::runParameterActions(const std::string& name)
{
auto it = _parameterTriggers.find(name);
if (it != _parameterTriggers.end())
{
std::for_each(it->second.begin(), it->second.end(),
[](auto& action) { action.get()->run(); });
}
}
} // namespace phosphor::fan::control::json