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
Event(const json & jsonObj,Manager * mgr,std::map<configKey,std::unique_ptr<Zone>> & zones)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
enable()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
powerOn()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
powerOff()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>>&
getAllGroups(bool loadGroups)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
configGroup(Group & group,const json & jsonObj)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
setGroups(const json & jsonObj,const std::vector<std::string> & profiles,std::vector<Group> & groups)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
setActions(const json & jsonObj)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
setTriggers(const json & jsonObj)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
dump() const315 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