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