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, _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, _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, std::vector<Group>& groups)
108 {
109     auto& availGroups = getAvailGroups();
110     for (const auto& jsonGrp : jsonObj["groups"])
111     {
112         if (!jsonGrp.contains("name"))
113         {
114             auto msg = fmt::format(
115                 "Missing required group name attribute in event {}", getName());
116             log<level::ERR>(msg.c_str(),
117                             entry("JSON=%s", jsonGrp.dump().c_str()));
118             throw std::runtime_error(msg.c_str());
119         }
120 
121         configKey eventProfile =
122             std::make_pair(jsonGrp["name"].get<std::string>(), _profiles);
123         auto grpEntry =
124             std::find_if(availGroups.begin(), availGroups.end(),
125                          [&eventProfile](const auto& grp) {
126                              return Manager::inConfig(grp.first, eventProfile);
127                          });
128         if (grpEntry != availGroups.end())
129         {
130             auto group = Group(*grpEntry->second);
131             configGroup(group, jsonGrp);
132             groups.emplace_back(group);
133         }
134     }
135 }
136 
137 void Event::setActions(const json& jsonObj)
138 {
139     for (const auto& jsonAct : jsonObj["actions"])
140     {
141         if (!jsonAct.contains("name"))
142         {
143             log<level::ERR>("Missing required event action name",
144                             entry("JSON=%s", jsonAct.dump().c_str()));
145             throw std::runtime_error("Missing required event action name");
146         }
147 
148         // Append action specific groups to the list of event groups for each
149         // action in the event
150         auto actionGroups = _groups;
151         if (jsonAct.contains("groups"))
152         {
153             setGroups(jsonAct, actionGroups);
154         }
155         if (actionGroups.empty())
156         {
157             log<level::DEBUG>(
158                 fmt::format("No groups configured for event {}'s action {} "
159                             "based on the active profile(s)",
160                             getName(), jsonAct["name"].get<std::string>())
161                     .c_str());
162         }
163 
164         // Determine list of zones action should be run against
165         std::vector<std::reference_wrapper<Zone>> actionZones;
166         if (!jsonAct.contains("zones"))
167         {
168             // No zones configured on the action results in the action running
169             // against all zones matching the event's active profiles
170             for (const auto& zone : _zones)
171             {
172                 configKey eventProfile =
173                     std::make_pair(zone.second->getName(), _profiles);
174                 auto zoneEntry = std::find_if(_zones.begin(), _zones.end(),
175                                               [&eventProfile](const auto& z) {
176                                                   return Manager::inConfig(
177                                                       z.first, eventProfile);
178                                               });
179                 if (zoneEntry != _zones.end())
180                 {
181                     actionZones.emplace_back(*zoneEntry->second);
182                 }
183             }
184         }
185         else
186         {
187             // Zones configured on the action result in the action only running
188             // against those zones if they match the event's active profiles
189             for (const auto& jsonZone : jsonAct["zones"])
190             {
191                 configKey eventProfile =
192                     std::make_pair(jsonZone.get<std::string>(), _profiles);
193                 auto zoneEntry = std::find_if(_zones.begin(), _zones.end(),
194                                               [&eventProfile](const auto& z) {
195                                                   return Manager::inConfig(
196                                                       z.first, eventProfile);
197                                               });
198                 if (zoneEntry != _zones.end())
199                 {
200                     actionZones.emplace_back(*zoneEntry->second);
201                 }
202             }
203         }
204         if (actionZones.empty())
205         {
206             log<level::DEBUG>(
207                 fmt::format("No zones configured for event {}'s action {} "
208                             "based on the active profile(s)",
209                             getName(), jsonAct["name"].get<std::string>())
210                     .c_str());
211         }
212 
213         // Create the action for the event
214         auto actObj = ActionFactory::getAction(
215             jsonAct["name"].get<std::string>(), jsonAct,
216             std::move(actionGroups), std::move(actionZones));
217         if (actObj)
218         {
219             _actions.emplace_back(std::move(actObj));
220         }
221     }
222 }
223 
224 void Event::setTriggers(const json& jsonObj)
225 {
226     if (!jsonObj.contains("triggers"))
227     {
228         log<level::ERR>("Missing required event triggers list",
229                         entry("JSON=%s", jsonObj.dump().c_str()));
230         throw std::runtime_error("Missing required event triggers list");
231     }
232     for (const auto& jsonTrig : jsonObj["triggers"])
233     {
234         if (!jsonTrig.contains("class"))
235         {
236             log<level::ERR>("Missing required event trigger class",
237                             entry("JSON=%s", jsonTrig.dump().c_str()));
238             throw std::runtime_error("Missing required event trigger class");
239         }
240         // The class of trigger used to run the event actions
241         auto tClass = jsonTrig["class"].get<std::string>();
242         std::transform(tClass.begin(), tClass.end(), tClass.begin(), tolower);
243         auto trigFunc = trigger::triggers.find(tClass);
244         if (trigFunc != trigger::triggers.end())
245         {
246             _triggers.emplace_back(
247                 trigFunc->second(jsonTrig, getName(), _actions));
248         }
249         else
250         {
251             // Construct list of available triggers
252             auto availTrigs = std::accumulate(
253                 std::next(trigger::triggers.begin()), trigger::triggers.end(),
254                 trigger::triggers.begin()->first, [](auto list, auto trig) {
255                     return std::move(list) + ", " + trig.first;
256                 });
257             log<level::ERR>(
258                 fmt::format("Trigger '{}' is not recognized", tClass).c_str(),
259                 entry("AVAILABLE_TRIGGERS=%s", availTrigs.c_str()));
260             throw std::runtime_error("Unsupported trigger class name given");
261         }
262     }
263 }
264 
265 } // namespace phosphor::fan::control::json
266