blob: 0e7f1e548b12b3ce82699ae5f556f8062e975512 [file] [log] [blame]
/**
* Copyright © 2020 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 "event.hpp"
#include "action.hpp"
#include "config_base.hpp"
#include "group.hpp"
#include "manager.hpp"
#include "sdbusplus.hpp"
#include "trigger.hpp"
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <algorithm>
#include <format>
#include <optional>
namespace phosphor::fan::control::json
{
using json = nlohmann::json;
using namespace phosphor::logging;
std::map<configKey, std::unique_ptr<Group>> Event::allGroups;
Event::Event(const json& jsonObj, Manager* mgr,
std::map<configKey, std::unique_ptr<Zone>>& zones) :
ConfigBase(jsonObj), _bus(util::SDBusPlus::getBus()), _manager(mgr),
_zones(zones)
{
// Event groups are optional
if (jsonObj.contains("groups"))
{
setGroups(jsonObj, _profiles, _groups);
}
// Event actions are optional
if (jsonObj.contains("actions"))
{
setActions(jsonObj);
}
setTriggers(jsonObj);
}
void Event::enable()
{
for (const auto& [type, trigger] : _triggers)
{
// Don't call the powerOn or powerOff triggers
if (type.find("power") == std::string::npos)
{
trigger(getName(), _manager, _groups, _actions);
}
}
}
void Event::powerOn()
{
for (const auto& [type, trigger] : _triggers)
{
if (type == "poweron")
{
trigger(getName(), _manager, _groups, _actions);
}
}
}
void Event::powerOff()
{
for (const auto& [type, trigger] : _triggers)
{
if (type == "poweroff")
{
trigger(getName(), _manager, _groups, _actions);
}
}
}
std::map<configKey, std::unique_ptr<Group>>&
Event::getAllGroups(bool loadGroups)
{
if (allGroups.empty() && loadGroups)
{
allGroups = Manager::getConfig<Group>(true);
}
return allGroups;
}
void Event::configGroup(Group& group, const json& jsonObj)
{
if (!jsonObj.contains("interface") || !jsonObj.contains("property") ||
!jsonObj["property"].contains("name"))
{
log<level::ERR>("Missing required group attribute",
entry("JSON=%s", jsonObj.dump().c_str()));
throw std::runtime_error("Missing required group attribute");
}
// Get the group members' interface
auto intf = jsonObj["interface"].get<std::string>();
group.setInterface(intf);
// Get the group members' property name
auto prop = jsonObj["property"]["name"].get<std::string>();
group.setProperty(prop);
// Get the group members' data type
if (jsonObj["property"].contains("type"))
{
std::optional<std::string> type =
jsonObj["property"]["type"].get<std::string>();
group.setType(type);
}
// Get the group members' expected value
if (jsonObj["property"].contains("value"))
{
std::optional<PropertyVariantType> value =
getJsonValue(jsonObj["property"]["value"]);
group.setValue(value);
}
}
void Event::setGroups(const json& jsonObj,
const std::vector<std::string>& profiles,
std::vector<Group>& groups)
{
if (jsonObj.contains("groups"))
{
auto& availGroups = getAllGroups();
for (const auto& jsonGrp : jsonObj["groups"])
{
if (!jsonGrp.contains("name"))
{
auto msg = std::format("Missing required group name attribute");
log<level::ERR>(msg.c_str(),
entry("JSON=%s", jsonGrp.dump().c_str()));
throw std::runtime_error(msg.c_str());
}
configKey eventProfile =
std::make_pair(jsonGrp["name"].get<std::string>(), profiles);
auto grpEntry = std::find_if(
availGroups.begin(), availGroups.end(),
[&eventProfile](const auto& grp) {
return Manager::inConfig(grp.first, eventProfile);
});
if (grpEntry != availGroups.end())
{
auto group = Group(*grpEntry->second);
configGroup(group, jsonGrp);
groups.emplace_back(group);
}
}
}
}
void Event::setActions(const json& jsonObj)
{
for (const auto& jsonAct : jsonObj["actions"])
{
if (!jsonAct.contains("name"))
{
log<level::ERR>("Missing required event action name",
entry("JSON=%s", jsonAct.dump().c_str()));
throw std::runtime_error("Missing required event action name");
}
// Determine list of zones action should be run against
std::vector<std::reference_wrapper<Zone>> actionZones;
if (!jsonAct.contains("zones"))
{
// No zones configured on the action results in the action running
// against all zones matching the event's active profiles
for (const auto& zone : _zones)
{
configKey eventProfile =
std::make_pair(zone.second->getName(), _profiles);
auto zoneEntry = std::find_if(
_zones.begin(), _zones.end(),
[&eventProfile](const auto& z) {
return Manager::inConfig(z.first, eventProfile);
});
if (zoneEntry != _zones.end())
{
actionZones.emplace_back(*zoneEntry->second);
}
}
}
else
{
// Zones configured on the action result in the action only running
// against those zones if they match the event's active profiles
for (const auto& jsonZone : jsonAct["zones"])
{
configKey eventProfile =
std::make_pair(jsonZone.get<std::string>(), _profiles);
auto zoneEntry = std::find_if(
_zones.begin(), _zones.end(),
[&eventProfile](const auto& z) {
return Manager::inConfig(z.first, eventProfile);
});
if (zoneEntry != _zones.end())
{
actionZones.emplace_back(*zoneEntry->second);
}
}
}
if (actionZones.empty())
{
log<level::DEBUG>(
std::format("No zones configured for event {}'s action {} "
"based on the active profile(s)",
getName(), jsonAct["name"].get<std::string>())
.c_str());
}
// Action specific groups, if any given, will override the use of event
// groups in the action(s)
std::vector<Group> actionGroups;
setGroups(jsonAct, _profiles, actionGroups);
if (!actionGroups.empty())
{
// Create the action for the event using the action's groups
auto actObj = ActionFactory::getAction(
jsonAct["name"].get<std::string>(), jsonAct,
std::move(actionGroups), std::move(actionZones));
if (actObj)
{
actObj->setEventName(_name);
_actions.emplace_back(std::move(actObj));
}
}
else
{
// Create the action for the event using the event's groups
auto actObj = ActionFactory::getAction(
jsonAct["name"].get<std::string>(), jsonAct, _groups,
std::move(actionZones));
if (actObj)
{
actObj->setEventName(_name);
_actions.emplace_back(std::move(actObj));
}
}
if (actionGroups.empty() && _groups.empty())
{
log<level::DEBUG>(
std::format("No groups configured for event {}'s action {} "
"based on the active profile(s)",
getName(), jsonAct["name"].get<std::string>())
.c_str());
}
}
}
void Event::setTriggers(const json& jsonObj)
{
if (!jsonObj.contains("triggers"))
{
log<level::ERR>("Missing required event triggers list",
entry("JSON=%s", jsonObj.dump().c_str()));
throw std::runtime_error("Missing required event triggers list");
}
for (const auto& jsonTrig : jsonObj["triggers"])
{
if (!jsonTrig.contains("class"))
{
log<level::ERR>("Missing required event trigger class",
entry("JSON=%s", jsonTrig.dump().c_str()));
throw std::runtime_error("Missing required event trigger class");
}
// The class of trigger used to run the event actions
auto tClass = jsonTrig["class"].get<std::string>();
std::transform(tClass.begin(), tClass.end(), tClass.begin(), tolower);
auto trigFunc = trigger::triggers.find(tClass);
if (trigFunc != trigger::triggers.end())
{
_triggers.emplace_back(
trigFunc->first,
trigFunc->second(jsonTrig, getName(), _actions));
}
else
{
// Construct list of available triggers
auto availTrigs = std::accumulate(
std::next(trigger::triggers.begin()), trigger::triggers.end(),
trigger::triggers.begin()->first, [](auto list, auto trig) {
return std::move(list) + ", " + trig.first;
});
log<level::ERR>(
std::format("Trigger '{}' is not recognized", tClass).c_str(),
entry("AVAILABLE_TRIGGERS=%s", availTrigs.c_str()));
throw std::runtime_error("Unsupported trigger class name given");
}
}
}
json Event::dump() const
{
json actionData;
std::for_each(_actions.begin(), _actions.end(),
[&actionData](const auto& action) {
actionData[action->getUniqueName()] = action->dump();
});
std::vector<std::string> groupData;
std::for_each(_groups.begin(), _groups.end(),
[&groupData](const auto& group) {
groupData.push_back(group.getName());
});
json eventData;
eventData["groups"] = groupData;
eventData["actions"] = actionData;
return eventData;
}
} // namespace phosphor::fan::control::json