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 <nlohmann/json.hpp> 26 #include <phosphor-logging/log.hpp> 27 #include <sdbusplus/bus.hpp> 28 29 #include <algorithm> 30 #include <format> 31 #include <optional> 32 33 namespace phosphor::fan::control::json 34 { 35 36 using json = nlohmann::json; 37 using namespace phosphor::logging; 38 39 std::map<configKey, std::unique_ptr<Group>> Event::allGroups; 40 41 Event::Event(const json& jsonObj, Manager* mgr, 42 std::map<configKey, std::unique_ptr<Zone>>& zones) : 43 ConfigBase(jsonObj), _bus(util::SDBusPlus::getBus()), _manager(mgr), 44 _zones(zones) 45 { 46 // Event groups are optional 47 if (jsonObj.contains("groups")) 48 { 49 setGroups(jsonObj, _profiles, _groups); 50 } 51 // Event actions are optional 52 if (jsonObj.contains("actions")) 53 { 54 setActions(jsonObj); 55 } 56 setTriggers(jsonObj); 57 } 58 59 void Event::enable() 60 { 61 for (const auto& [type, trigger] : _triggers) 62 { 63 // Don't call the powerOn or powerOff triggers 64 if (type.find("power") == std::string::npos) 65 { 66 trigger(getName(), _manager, _groups, _actions); 67 } 68 } 69 } 70 71 void Event::powerOn() 72 { 73 for (const auto& [type, trigger] : _triggers) 74 { 75 if (type == "poweron") 76 { 77 trigger(getName(), _manager, _groups, _actions); 78 } 79 } 80 } 81 82 void Event::powerOff() 83 { 84 for (const auto& [type, trigger] : _triggers) 85 { 86 if (type == "poweroff") 87 { 88 trigger(getName(), _manager, _groups, _actions); 89 } 90 } 91 } 92 93 std::map<configKey, std::unique_ptr<Group>>& 94 Event::getAllGroups(bool loadGroups) 95 { 96 if (allGroups.empty() && loadGroups) 97 { 98 allGroups = Manager::getConfig<Group>(true); 99 } 100 101 return allGroups; 102 } 103 104 void Event::configGroup(Group& group, const json& jsonObj) 105 { 106 if (!jsonObj.contains("interface") || !jsonObj.contains("property") || 107 !jsonObj["property"].contains("name")) 108 { 109 log<level::ERR>("Missing required group attribute", 110 entry("JSON=%s", jsonObj.dump().c_str())); 111 throw std::runtime_error("Missing required group attribute"); 112 } 113 114 // Get the group members' interface 115 auto intf = jsonObj["interface"].get<std::string>(); 116 group.setInterface(intf); 117 118 // Get the group members' property name 119 auto prop = jsonObj["property"]["name"].get<std::string>(); 120 group.setProperty(prop); 121 122 // Get the group members' data type 123 if (jsonObj["property"].contains("type")) 124 { 125 std::optional<std::string> type = 126 jsonObj["property"]["type"].get<std::string>(); 127 group.setType(type); 128 } 129 130 // Get the group members' expected value 131 if (jsonObj["property"].contains("value")) 132 { 133 std::optional<PropertyVariantType> value = 134 getJsonValue(jsonObj["property"]["value"]); 135 group.setValue(value); 136 } 137 } 138 139 void Event::setGroups(const json& jsonObj, 140 const std::vector<std::string>& profiles, 141 std::vector<Group>& groups) 142 { 143 if (jsonObj.contains("groups")) 144 { 145 auto& availGroups = getAllGroups(); 146 for (const auto& jsonGrp : jsonObj["groups"]) 147 { 148 if (!jsonGrp.contains("name")) 149 { 150 auto msg = std::format("Missing required group name attribute"); 151 log<level::ERR>(msg.c_str(), 152 entry("JSON=%s", jsonGrp.dump().c_str())); 153 throw std::runtime_error(msg.c_str()); 154 } 155 156 configKey eventProfile = 157 std::make_pair(jsonGrp["name"].get<std::string>(), profiles); 158 auto grpEntry = std::find_if( 159 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 = 193 std::make_pair(zone.second->getName(), _profiles); 194 auto zoneEntry = std::find_if( 195 _zones.begin(), _zones.end(), 196 [&eventProfile](const auto& z) { 197 return Manager::inConfig(z.first, eventProfile); 198 }); 199 if (zoneEntry != _zones.end()) 200 { 201 actionZones.emplace_back(*zoneEntry->second); 202 } 203 } 204 } 205 else 206 { 207 // Zones configured on the action result in the action only running 208 // against those zones if they match the event's active profiles 209 for (const auto& jsonZone : jsonAct["zones"]) 210 { 211 configKey eventProfile = 212 std::make_pair(jsonZone.get<std::string>(), _profiles); 213 auto zoneEntry = std::find_if( 214 _zones.begin(), _zones.end(), 215 [&eventProfile](const auto& z) { 216 return Manager::inConfig(z.first, eventProfile); 217 }); 218 if (zoneEntry != _zones.end()) 219 { 220 actionZones.emplace_back(*zoneEntry->second); 221 } 222 } 223 } 224 if (actionZones.empty()) 225 { 226 log<level::DEBUG>( 227 std::format("No zones configured for event {}'s action {} " 228 "based on the active profile(s)", 229 getName(), jsonAct["name"].get<std::string>()) 230 .c_str()); 231 } 232 233 // Action specific groups, if any given, will override the use of event 234 // groups in the action(s) 235 std::vector<Group> actionGroups; 236 setGroups(jsonAct, _profiles, actionGroups); 237 if (!actionGroups.empty()) 238 { 239 // Create the action for the event using the action's groups 240 auto actObj = ActionFactory::getAction( 241 jsonAct["name"].get<std::string>(), jsonAct, 242 std::move(actionGroups), std::move(actionZones)); 243 if (actObj) 244 { 245 actObj->setEventName(_name); 246 _actions.emplace_back(std::move(actObj)); 247 } 248 } 249 else 250 { 251 // Create the action for the event using the event's groups 252 auto actObj = ActionFactory::getAction( 253 jsonAct["name"].get<std::string>(), jsonAct, _groups, 254 std::move(actionZones)); 255 if (actObj) 256 { 257 actObj->setEventName(_name); 258 _actions.emplace_back(std::move(actObj)); 259 } 260 } 261 262 if (actionGroups.empty() && _groups.empty()) 263 { 264 log<level::DEBUG>( 265 std::format("No groups configured for event {}'s action {} " 266 "based on the active profile(s)", 267 getName(), jsonAct["name"].get<std::string>()) 268 .c_str()); 269 } 270 } 271 } 272 273 void Event::setTriggers(const json& jsonObj) 274 { 275 if (!jsonObj.contains("triggers")) 276 { 277 log<level::ERR>("Missing required event triggers list", 278 entry("JSON=%s", jsonObj.dump().c_str())); 279 throw std::runtime_error("Missing required event triggers list"); 280 } 281 for (const auto& jsonTrig : jsonObj["triggers"]) 282 { 283 if (!jsonTrig.contains("class")) 284 { 285 log<level::ERR>("Missing required event trigger class", 286 entry("JSON=%s", jsonTrig.dump().c_str())); 287 throw std::runtime_error("Missing required event trigger class"); 288 } 289 // The class of trigger used to run the event actions 290 auto tClass = jsonTrig["class"].get<std::string>(); 291 std::transform(tClass.begin(), tClass.end(), tClass.begin(), tolower); 292 auto trigFunc = trigger::triggers.find(tClass); 293 if (trigFunc != trigger::triggers.end()) 294 { 295 _triggers.emplace_back( 296 trigFunc->first, 297 trigFunc->second(jsonTrig, getName(), _actions)); 298 } 299 else 300 { 301 // Construct list of available triggers 302 auto availTrigs = std::accumulate( 303 std::next(trigger::triggers.begin()), trigger::triggers.end(), 304 trigger::triggers.begin()->first, [](auto list, auto trig) { 305 return std::move(list) + ", " + trig.first; 306 }); 307 log<level::ERR>( 308 std::format("Trigger '{}' is not recognized", tClass).c_str(), 309 entry("AVAILABLE_TRIGGERS=%s", availTrigs.c_str())); 310 throw std::runtime_error("Unsupported trigger class name given"); 311 } 312 } 313 } 314 315 json Event::dump() const 316 { 317 json actionData; 318 std::for_each(_actions.begin(), _actions.end(), 319 [&actionData](const auto& action) { 320 actionData[action->getUniqueName()] = action->dump(); 321 }); 322 323 std::vector<std::string> groupData; 324 std::for_each(_groups.begin(), _groups.end(), 325 [&groupData](const auto& group) { 326 groupData.push_back(group.getName()); 327 }); 328 329 json eventData; 330 eventData["groups"] = groupData; 331 eventData["actions"] = actionData; 332 333 return eventData; 334 } 335 336 } // namespace phosphor::fan::control::json 337