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