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