/** * 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 #include #include #include #include #include namespace phosphor::fan::control::json { using json = nlohmann::json; using namespace phosphor::logging; std::map> Event::allGroups; Event::Event(const json& jsonObj, Manager* mgr, std::map>& 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>& Event::getAllGroups(bool loadGroups) { if (allGroups.empty() && loadGroups) { allGroups = Manager::getConfig(true); } return allGroups; } void Event::configGroup(Group& group, const json& jsonObj) { if (!jsonObj.contains("interface") || !jsonObj.contains("property") || !jsonObj["property"].contains("name")) { log("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(); group.setInterface(intf); // Get the group members' property name auto prop = jsonObj["property"]["name"].get(); group.setProperty(prop); // Get the group members' data type if (jsonObj["property"].contains("type")) { std::optional type = jsonObj["property"]["type"].get(); group.setType(type); } // Get the group members' expected value if (jsonObj["property"].contains("value")) { std::optional value = getJsonValue(jsonObj["property"]["value"]); group.setValue(value); } } void Event::setGroups(const json& jsonObj, const std::vector& profiles, std::vector& groups) { if (jsonObj.contains("groups")) { auto& availGroups = getAllGroups(); for (const auto& jsonGrp : jsonObj["groups"]) { if (!jsonGrp.contains("name")) { auto msg = fmt::format("Missing required group name attribute"); log(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(), 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("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> 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(), _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( fmt::format("No zones configured for event {}'s action {} " "based on the active profile(s)", getName(), jsonAct["name"].get()) .c_str()); } // Action specific groups, if any given, will override the use of event // groups in the action(s) std::vector 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(), 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(), jsonAct, _groups, std::move(actionZones)); if (actObj) { actObj->setEventName(_name); _actions.emplace_back(std::move(actObj)); } } if (actionGroups.empty() && _groups.empty()) { log( fmt::format("No groups configured for event {}'s action {} " "based on the active profile(s)", getName(), jsonAct["name"].get()) .c_str()); } } } void Event::setTriggers(const json& jsonObj) { if (!jsonObj.contains("triggers")) { log("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("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::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( fmt::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 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