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