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