1 /** 2 * Copyright © 2020 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "event.hpp" 17 18 #include "action.hpp" 19 #include "config_base.hpp" 20 #include "group.hpp" 21 #include "manager.hpp" 22 #include "sdbusplus.hpp" 23 #include "trigger.hpp" 24 25 #include <fmt/format.h> 26 27 #include <nlohmann/json.hpp> 28 #include <phosphor-logging/log.hpp> 29 #include <sdbusplus/bus.hpp> 30 31 #include <algorithm> 32 #include <optional> 33 34 namespace phosphor::fan::control::json 35 { 36 37 using json = nlohmann::json; 38 using namespace phosphor::logging; 39 40 std::map<configKey, std::unique_ptr<Group>> Event::allGroups; 41 42 Event::Event(const json& jsonObj, Manager* mgr, 43 std::map<configKey, std::unique_ptr<Zone>>& zones) : 44 ConfigBase(jsonObj), 45 _bus(util::SDBusPlus::getBus()), _manager(mgr), _zones(zones) 46 { 47 // Event groups are optional 48 if (jsonObj.contains("groups")) 49 { 50 setGroups(jsonObj, _profiles, _groups); 51 } 52 // Event actions are optional 53 if (jsonObj.contains("actions")) 54 { 55 setActions(jsonObj); 56 } 57 setTriggers(jsonObj); 58 } 59 60 void Event::enable() 61 { 62 for (const auto& [type, trigger] : _triggers) 63 { 64 // Don't call the powerOn or powerOff triggers 65 if (type.find("power") == std::string::npos) 66 { 67 trigger(getName(), _manager, _groups, _actions); 68 } 69 } 70 } 71 72 void Event::powerOn() 73 { 74 for (const auto& [type, trigger] : _triggers) 75 { 76 if (type == "poweron") 77 { 78 trigger(getName(), _manager, _groups, _actions); 79 } 80 } 81 } 82 83 void Event::powerOff() 84 { 85 for (const auto& [type, trigger] : _triggers) 86 { 87 if (type == "poweroff") 88 { 89 trigger(getName(), _manager, _groups, _actions); 90 } 91 } 92 } 93 94 std::map<configKey, std::unique_ptr<Group>>& 95 Event::getAllGroups(bool loadGroups) 96 { 97 if (allGroups.empty() && loadGroups) 98 { 99 allGroups = Manager::getConfig<Group>(true); 100 } 101 102 return allGroups; 103 } 104 105 void Event::configGroup(Group& group, const json& jsonObj) 106 { 107 if (!jsonObj.contains("interface") || !jsonObj.contains("property") || 108 !jsonObj["property"].contains("name")) 109 { 110 log<level::ERR>("Missing required group attribute", 111 entry("JSON=%s", jsonObj.dump().c_str())); 112 throw std::runtime_error("Missing required group attribute"); 113 } 114 115 // Get the group members' interface 116 auto intf = jsonObj["interface"].get<std::string>(); 117 group.setInterface(intf); 118 119 // Get the group members' property name 120 auto prop = jsonObj["property"]["name"].get<std::string>(); 121 group.setProperty(prop); 122 123 // Get the group members' data type 124 if (jsonObj["property"].contains("type")) 125 { 126 std::optional<std::string> type = 127 jsonObj["property"]["type"].get<std::string>(); 128 group.setType(type); 129 } 130 131 // Get the group members' expected value 132 if (jsonObj["property"].contains("value")) 133 { 134 std::optional<PropertyVariantType> value = 135 getJsonValue(jsonObj["property"]["value"]); 136 group.setValue(value); 137 } 138 } 139 140 void Event::setGroups(const json& jsonObj, 141 const std::vector<std::string>& profiles, 142 std::vector<Group>& groups) 143 { 144 if (jsonObj.contains("groups")) 145 { 146 auto& availGroups = getAllGroups(); 147 for (const auto& jsonGrp : jsonObj["groups"]) 148 { 149 if (!jsonGrp.contains("name")) 150 { 151 auto msg = fmt::format("Missing required group name attribute"); 152 log<level::ERR>(msg.c_str(), 153 entry("JSON=%s", jsonGrp.dump().c_str())); 154 throw std::runtime_error(msg.c_str()); 155 } 156 157 configKey eventProfile = 158 std::make_pair(jsonGrp["name"].get<std::string>(), profiles); 159 auto grpEntry = std::find_if(availGroups.begin(), availGroups.end(), 160 [&eventProfile](const auto& grp) { 161 return Manager::inConfig(grp.first, eventProfile); 162 }); 163 if (grpEntry != availGroups.end()) 164 { 165 auto group = Group(*grpEntry->second); 166 configGroup(group, jsonGrp); 167 groups.emplace_back(group); 168 } 169 } 170 } 171 } 172 173 void Event::setActions(const json& jsonObj) 174 { 175 for (const auto& jsonAct : jsonObj["actions"]) 176 { 177 if (!jsonAct.contains("name")) 178 { 179 log<level::ERR>("Missing required event action name", 180 entry("JSON=%s", jsonAct.dump().c_str())); 181 throw std::runtime_error("Missing required event action name"); 182 } 183 184 // Determine list of zones action should be run against 185 std::vector<std::reference_wrapper<Zone>> actionZones; 186 if (!jsonAct.contains("zones")) 187 { 188 // No zones configured on the action results in the action running 189 // against all zones matching the event's active profiles 190 for (const auto& zone : _zones) 191 { 192 configKey eventProfile = std::make_pair(zone.second->getName(), 193 _profiles); 194 auto zoneEntry = std::find_if(_zones.begin(), _zones.end(), 195 [&eventProfile](const auto& z) { 196 return Manager::inConfig(z.first, eventProfile); 197 }); 198 if (zoneEntry != _zones.end()) 199 { 200 actionZones.emplace_back(*zoneEntry->second); 201 } 202 } 203 } 204 else 205 { 206 // Zones configured on the action result in the action only running 207 // against those zones if they match the event's active profiles 208 for (const auto& jsonZone : jsonAct["zones"]) 209 { 210 configKey eventProfile = 211 std::make_pair(jsonZone.get<std::string>(), _profiles); 212 auto zoneEntry = std::find_if(_zones.begin(), _zones.end(), 213 [&eventProfile](const auto& z) { 214 return Manager::inConfig(z.first, eventProfile); 215 }); 216 if (zoneEntry != _zones.end()) 217 { 218 actionZones.emplace_back(*zoneEntry->second); 219 } 220 } 221 } 222 if (actionZones.empty()) 223 { 224 log<level::DEBUG>( 225 fmt::format("No zones configured for event {}'s action {} " 226 "based on the active profile(s)", 227 getName(), jsonAct["name"].get<std::string>()) 228 .c_str()); 229 } 230 231 // Action specific groups, if any given, will override the use of event 232 // groups in the action(s) 233 std::vector<Group> actionGroups; 234 setGroups(jsonAct, _profiles, actionGroups); 235 if (!actionGroups.empty()) 236 { 237 // Create the action for the event using the action's groups 238 auto actObj = ActionFactory::getAction( 239 jsonAct["name"].get<std::string>(), jsonAct, 240 std::move(actionGroups), std::move(actionZones)); 241 if (actObj) 242 { 243 actObj->setEventName(_name); 244 _actions.emplace_back(std::move(actObj)); 245 } 246 } 247 else 248 { 249 // Create the action for the event using the event's groups 250 auto actObj = ActionFactory::getAction( 251 jsonAct["name"].get<std::string>(), jsonAct, _groups, 252 std::move(actionZones)); 253 if (actObj) 254 { 255 actObj->setEventName(_name); 256 _actions.emplace_back(std::move(actObj)); 257 } 258 } 259 260 if (actionGroups.empty() && _groups.empty()) 261 { 262 log<level::DEBUG>( 263 fmt::format("No groups configured for event {}'s action {} " 264 "based on the active profile(s)", 265 getName(), jsonAct["name"].get<std::string>()) 266 .c_str()); 267 } 268 } 269 } 270 271 void Event::setTriggers(const json& jsonObj) 272 { 273 if (!jsonObj.contains("triggers")) 274 { 275 log<level::ERR>("Missing required event triggers list", 276 entry("JSON=%s", jsonObj.dump().c_str())); 277 throw std::runtime_error("Missing required event triggers list"); 278 } 279 for (const auto& jsonTrig : jsonObj["triggers"]) 280 { 281 if (!jsonTrig.contains("class")) 282 { 283 log<level::ERR>("Missing required event trigger class", 284 entry("JSON=%s", jsonTrig.dump().c_str())); 285 throw std::runtime_error("Missing required event trigger class"); 286 } 287 // The class of trigger used to run the event actions 288 auto tClass = jsonTrig["class"].get<std::string>(); 289 std::transform(tClass.begin(), tClass.end(), tClass.begin(), tolower); 290 auto trigFunc = trigger::triggers.find(tClass); 291 if (trigFunc != trigger::triggers.end()) 292 { 293 _triggers.emplace_back( 294 trigFunc->first, 295 trigFunc->second(jsonTrig, getName(), _actions)); 296 } 297 else 298 { 299 // Construct list of available triggers 300 auto availTrigs = std::accumulate( 301 std::next(trigger::triggers.begin()), trigger::triggers.end(), 302 trigger::triggers.begin()->first, [](auto list, auto trig) { 303 return std::move(list) + ", " + trig.first; 304 }); 305 log<level::ERR>( 306 fmt::format("Trigger '{}' is not recognized", tClass).c_str(), 307 entry("AVAILABLE_TRIGGERS=%s", availTrigs.c_str())); 308 throw std::runtime_error("Unsupported trigger class name given"); 309 } 310 } 311 } 312 313 json Event::dump() const 314 { 315 json actionData; 316 std::for_each(_actions.begin(), _actions.end(), 317 [&actionData](const auto& action) { 318 actionData[action->getUniqueName()] = action->dump(); 319 }); 320 321 std::vector<std::string> groupData; 322 std::for_each(_groups.begin(), _groups.end(), 323 [&groupData](const auto& group) { 324 groupData.push_back(group.getName()); 325 }); 326 327 json eventData; 328 eventData["groups"] = groupData; 329 eventData["actions"] = actionData; 330 331 return eventData; 332 } 333 334 } // namespace phosphor::fan::control::json 335