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