| /** |
| * 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 |