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& trigger : _triggers) 63 { 64 trigger(getName(), _manager, _groups, _actions); 65 } 66 } 67 68 std::map<configKey, std::unique_ptr<Group>>& 69 Event::getAllGroups(bool loadGroups) 70 { 71 if (allGroups.empty() && loadGroups) 72 { 73 allGroups = Manager::getConfig<Group>(true); 74 } 75 76 return allGroups; 77 } 78 79 void Event::configGroup(Group& group, const json& jsonObj) 80 { 81 if (!jsonObj.contains("interface") || !jsonObj.contains("property") || 82 !jsonObj["property"].contains("name")) 83 { 84 log<level::ERR>("Missing required group attribute", 85 entry("JSON=%s", jsonObj.dump().c_str())); 86 throw std::runtime_error("Missing required group attribute"); 87 } 88 89 // Get the group members' interface 90 auto intf = jsonObj["interface"].get<std::string>(); 91 group.setInterface(intf); 92 93 // Get the group members' property name 94 auto prop = jsonObj["property"]["name"].get<std::string>(); 95 group.setProperty(prop); 96 97 // Get the group members' data type 98 if (jsonObj["property"].contains("type")) 99 { 100 std::optional<std::string> type = 101 jsonObj["property"]["type"].get<std::string>(); 102 group.setType(type); 103 } 104 105 // Get the group members' expected value 106 if (jsonObj["property"].contains("value")) 107 { 108 std::optional<PropertyVariantType> value = 109 getJsonValue(jsonObj["property"]["value"]); 110 group.setValue(value); 111 } 112 } 113 114 void Event::setGroups(const json& jsonObj, 115 const std::vector<std::string>& profiles, 116 std::vector<Group>& groups) 117 { 118 if (jsonObj.contains("groups")) 119 { 120 auto& availGroups = getAllGroups(); 121 for (const auto& jsonGrp : jsonObj["groups"]) 122 { 123 if (!jsonGrp.contains("name")) 124 { 125 auto msg = fmt::format("Missing required group name attribute"); 126 log<level::ERR>(msg.c_str(), 127 entry("JSON=%s", jsonGrp.dump().c_str())); 128 throw std::runtime_error(msg.c_str()); 129 } 130 131 configKey eventProfile = 132 std::make_pair(jsonGrp["name"].get<std::string>(), profiles); 133 auto grpEntry = std::find_if(availGroups.begin(), availGroups.end(), 134 [&eventProfile](const auto& grp) { 135 return Manager::inConfig( 136 grp.first, eventProfile); 137 }); 138 if (grpEntry != availGroups.end()) 139 { 140 auto group = Group(*grpEntry->second); 141 configGroup(group, jsonGrp); 142 groups.emplace_back(group); 143 } 144 } 145 } 146 } 147 148 void Event::setActions(const json& jsonObj) 149 { 150 for (const auto& jsonAct : jsonObj["actions"]) 151 { 152 if (!jsonAct.contains("name")) 153 { 154 log<level::ERR>("Missing required event action name", 155 entry("JSON=%s", jsonAct.dump().c_str())); 156 throw std::runtime_error("Missing required event action name"); 157 } 158 159 // Determine list of zones action should be run against 160 std::vector<std::reference_wrapper<Zone>> actionZones; 161 if (!jsonAct.contains("zones")) 162 { 163 // No zones configured on the action results in the action running 164 // against all zones matching the event's active profiles 165 for (const auto& zone : _zones) 166 { 167 configKey eventProfile = 168 std::make_pair(zone.second->getName(), _profiles); 169 auto zoneEntry = std::find_if(_zones.begin(), _zones.end(), 170 [&eventProfile](const auto& z) { 171 return Manager::inConfig( 172 z.first, eventProfile); 173 }); 174 if (zoneEntry != _zones.end()) 175 { 176 actionZones.emplace_back(*zoneEntry->second); 177 } 178 } 179 } 180 else 181 { 182 // Zones configured on the action result in the action only running 183 // against those zones if they match the event's active profiles 184 for (const auto& jsonZone : jsonAct["zones"]) 185 { 186 configKey eventProfile = 187 std::make_pair(jsonZone.get<std::string>(), _profiles); 188 auto zoneEntry = std::find_if(_zones.begin(), _zones.end(), 189 [&eventProfile](const auto& z) { 190 return Manager::inConfig( 191 z.first, eventProfile); 192 }); 193 if (zoneEntry != _zones.end()) 194 { 195 actionZones.emplace_back(*zoneEntry->second); 196 } 197 } 198 } 199 if (actionZones.empty()) 200 { 201 log<level::DEBUG>( 202 fmt::format("No zones configured for event {}'s action {} " 203 "based on the active profile(s)", 204 getName(), jsonAct["name"].get<std::string>()) 205 .c_str()); 206 } 207 208 // Action specific groups, if any given, will override the use of event 209 // groups in the action(s) 210 std::vector<Group> actionGroups; 211 setGroups(jsonAct, _profiles, actionGroups); 212 if (!actionGroups.empty()) 213 { 214 // Create the action for the event using the action's groups 215 auto actObj = ActionFactory::getAction( 216 jsonAct["name"].get<std::string>(), jsonAct, 217 std::move(actionGroups), std::move(actionZones)); 218 if (actObj) 219 { 220 _actions.emplace_back(std::move(actObj)); 221 } 222 } 223 else 224 { 225 // Create the action for the event using the event's groups 226 auto actObj = ActionFactory::getAction( 227 jsonAct["name"].get<std::string>(), jsonAct, _groups, 228 std::move(actionZones)); 229 if (actObj) 230 { 231 _actions.emplace_back(std::move(actObj)); 232 } 233 } 234 235 if (actionGroups.empty() && _groups.empty()) 236 { 237 log<level::DEBUG>( 238 fmt::format("No groups configured for event {}'s action {} " 239 "based on the active profile(s)", 240 getName(), jsonAct["name"].get<std::string>()) 241 .c_str()); 242 } 243 } 244 } 245 246 void Event::setTriggers(const json& jsonObj) 247 { 248 if (!jsonObj.contains("triggers")) 249 { 250 log<level::ERR>("Missing required event triggers list", 251 entry("JSON=%s", jsonObj.dump().c_str())); 252 throw std::runtime_error("Missing required event triggers list"); 253 } 254 for (const auto& jsonTrig : jsonObj["triggers"]) 255 { 256 if (!jsonTrig.contains("class")) 257 { 258 log<level::ERR>("Missing required event trigger class", 259 entry("JSON=%s", jsonTrig.dump().c_str())); 260 throw std::runtime_error("Missing required event trigger class"); 261 } 262 // The class of trigger used to run the event actions 263 auto tClass = jsonTrig["class"].get<std::string>(); 264 std::transform(tClass.begin(), tClass.end(), tClass.begin(), tolower); 265 auto trigFunc = trigger::triggers.find(tClass); 266 if (trigFunc != trigger::triggers.end()) 267 { 268 _triggers.emplace_back( 269 trigFunc->second(jsonTrig, getName(), _actions)); 270 } 271 else 272 { 273 // Construct list of available triggers 274 auto availTrigs = std::accumulate( 275 std::next(trigger::triggers.begin()), trigger::triggers.end(), 276 trigger::triggers.begin()->first, [](auto list, auto trig) { 277 return std::move(list) + ", " + trig.first; 278 }); 279 log<level::ERR>( 280 fmt::format("Trigger '{}' is not recognized", tClass).c_str(), 281 entry("AVAILABLE_TRIGGERS=%s", availTrigs.c_str())); 282 throw std::runtime_error("Unsupported trigger class name given"); 283 } 284 } 285 } 286 287 } // namespace phosphor::fan::control::json 288