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