xref: /openbmc/phosphor-fan-presence/control/json/event.cpp (revision c7f07a223669091df0878370293e65dd6b0afdeb)
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