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