blob: 409006a2ca67af588994195e572e5c8c2a9239fa [file] [log] [blame]
Matthew Barth3174e722020-09-15 15:13:40 -05001/**
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
Matthew Barth776ca562021-03-31 09:50:58 -050018#include "action.hpp"
Matthew Barthe5578602021-03-30 12:53:24 -050019#include "config_base.hpp"
Matthew Barth391ade02021-01-15 14:33:21 -060020#include "group.hpp"
Matthew Barthe5578602021-03-30 12:53:24 -050021#include "manager.hpp"
Matthew Barth9403a212021-05-17 09:31:50 -050022#include "sdbusplus.hpp"
Matthew Barth54b5a242021-05-21 11:02:52 -050023#include "trigger.hpp"
Matthew Barthe5578602021-03-30 12:53:24 -050024
25#include <fmt/format.h>
Matthew Barth391ade02021-01-15 14:33:21 -060026
Matthew Barth3174e722020-09-15 15:13:40 -050027#include <nlohmann/json.hpp>
28#include <phosphor-logging/log.hpp>
29#include <sdbusplus/bus.hpp>
30
Matthew Barthe5578602021-03-30 12:53:24 -050031#include <algorithm>
Matthew Barth12bae962021-01-15 16:18:11 -060032#include <optional>
Matthew Barth12bae962021-01-15 16:18:11 -060033
Matthew Barth3174e722020-09-15 15:13:40 -050034namespace phosphor::fan::control::json
35{
36
37using json = nlohmann::json;
38using namespace phosphor::logging;
39
Matthew Barth3695ac32021-10-06 14:55:30 -050040std::map<configKey, std::unique_ptr<Group>> Event::allGroups;
41
Matthew Barth9403a212021-05-17 09:31:50 -050042Event::Event(const json& jsonObj, Manager* mgr,
Matthew Barth9f1632e2021-03-31 15:51:50 -050043 std::map<configKey, std::unique_ptr<Zone>>& zones) :
Matthew Barth44ab7692021-03-26 11:40:10 -050044 ConfigBase(jsonObj),
Matthew Barth9403a212021-05-17 09:31:50 -050045 _bus(util::SDBusPlus::getBus()), _manager(mgr), _zones(zones)
Matthew Barth3174e722020-09-15 15:13:40 -050046{
Matthew Barth619dc0f2021-05-20 09:27:02 -050047 // Event groups are optional
48 if (jsonObj.contains("groups"))
Matthew Barth3174e722020-09-15 15:13:40 -050049 {
Matthew Barthe386fbb2021-06-30 14:33:55 -050050 setGroups(jsonObj, _profiles, _groups);
Matthew Barth3174e722020-09-15 15:13:40 -050051 }
Matthew Barth619dc0f2021-05-20 09:27:02 -050052 // Event actions are optional
53 if (jsonObj.contains("actions"))
Matthew Barth3174e722020-09-15 15:13:40 -050054 {
Matthew Barth619dc0f2021-05-20 09:27:02 -050055 setActions(jsonObj);
Matthew Barth3174e722020-09-15 15:13:40 -050056 }
Matthew Barth619dc0f2021-05-20 09:27:02 -050057 setTriggers(jsonObj);
Matthew Barth3174e722020-09-15 15:13:40 -050058}
59
Matthew Barth54b5a242021-05-21 11:02:52 -050060void Event::enable()
61{
Matt Spinlerd1f97f42021-10-29 16:19:24 -050062 for (const auto& [type, trigger] : _triggers)
Matthew Barth54b5a242021-05-21 11:02:52 -050063 {
Matt Spinlerd1f97f42021-10-29 16:19:24 -050064 // 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
72void 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
83void Event::powerOff()
84{
85 for (const auto& [type, trigger] : _triggers)
86 {
87 if (type == "poweroff")
88 {
89 trigger(getName(), _manager, _groups, _actions);
90 }
Matthew Barth54b5a242021-05-21 11:02:52 -050091 }
92}
93
Matthew Barth3695ac32021-10-06 14:55:30 -050094std::map<configKey, std::unique_ptr<Group>>&
95 Event::getAllGroups(bool loadGroups)
Matthew Barthc8bde4a2021-05-19 15:34:49 -050096{
Matthew Barth3695ac32021-10-06 14:55:30 -050097 if (allGroups.empty() && loadGroups)
98 {
99 allGroups = Manager::getConfig<Group>(true);
100 }
101
102 return allGroups;
Matthew Barthc8bde4a2021-05-19 15:34:49 -0500103}
104
Matthew Barth46b34482021-04-06 11:27:23 -0500105void 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
Matthew Barthe386fbb2021-06-30 14:33:55 -0500140void Event::setGroups(const json& jsonObj,
141 const std::vector<std::string>& profiles,
142 std::vector<Group>& groups)
Matthew Barth3174e722020-09-15 15:13:40 -0500143{
Matthew Barthe386fbb2021-06-30 14:33:55 -0500144 if (jsonObj.contains("groups"))
Matthew Barth3174e722020-09-15 15:13:40 -0500145 {
Matthew Barth3695ac32021-10-06 14:55:30 -0500146 auto& availGroups = getAllGroups();
Matthew Barthe386fbb2021-06-30 14:33:55 -0500147 for (const auto& jsonGrp : jsonObj["groups"])
Matthew Barth3174e722020-09-15 15:13:40 -0500148 {
Matthew Barthe386fbb2021-06-30 14:33:55 -0500149 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 }
Matthew Barth12bae962021-01-15 16:18:11 -0600156
Matthew Barthe386fbb2021-06-30 14:33:55 -0500157 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 }
Matthew Barth12bae962021-01-15 16:18:11 -0600170 }
Matthew Barthe5578602021-03-30 12:53:24 -0500171 }
Matthew Barth3174e722020-09-15 15:13:40 -0500172}
173
Matthew Barthc8bde4a2021-05-19 15:34:49 -0500174void Event::setActions(const json& jsonObj)
Matthew Barth3174e722020-09-15 15:13:40 -0500175{
Matthew Barth46b34482021-04-06 11:27:23 -0500176 for (const auto& jsonAct : jsonObj["actions"])
Matthew Barth3174e722020-09-15 15:13:40 -0500177 {
Matthew Barth46b34482021-04-06 11:27:23 -0500178 if (!jsonAct.contains("name"))
Matthew Barth3174e722020-09-15 15:13:40 -0500179 {
180 log<level::ERR>("Missing required event action name",
Matthew Barth46b34482021-04-06 11:27:23 -0500181 entry("JSON=%s", jsonAct.dump().c_str()));
Matthew Barth3174e722020-09-15 15:13:40 -0500182 throw std::runtime_error("Missing required event action name");
183 }
Matthew Barth46b34482021-04-06 11:27:23 -0500184
Matthew Barth46b34482021-04-06 11:27:23 -0500185 // Determine list of zones action should be run against
186 std::vector<std::reference_wrapper<Zone>> actionZones;
Matthew Bartha8ea0912021-05-03 14:33:52 -0500187 if (!jsonAct.contains("zones"))
Matthew Barth46b34482021-04-06 11:27:23 -0500188 {
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
Matthew Bartha8ea0912021-05-03 14:33:52 -0500210 for (const auto& jsonZone : jsonAct["zones"])
Matthew Barth46b34482021-04-06 11:27:23 -0500211 {
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
Matthew Barth5fb52682021-09-29 13:57:02 -0500234 // 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())
Matthew Barth776ca562021-03-31 09:50:58 -0500239 {
Matthew Barth5fb52682021-09-29 13:57:02 -0500240 // 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 {
Matt Spinlere6fc2102022-04-07 14:31:29 -0500246 actObj->setEventName(_name);
Matthew Barth5fb52682021-09-29 13:57:02 -0500247 _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 {
Matt Spinlere6fc2102022-04-07 14:31:29 -0500258 actObj->setEventName(_name);
Matthew Barth5fb52682021-09-29 13:57:02 -0500259 _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());
Matthew Barth776ca562021-03-31 09:50:58 -0500270 }
Matthew Barth3174e722020-09-15 15:13:40 -0500271 }
272}
273
Matthew Barth9f1632e2021-03-31 15:51:50 -0500274void 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 }
Matthew Barth0620be72021-04-14 13:31:12 -0500282 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 {
Matthew Barth54b5a242021-05-21 11:02:52 -0500296 _triggers.emplace_back(
Matt Spinlerd1f97f42021-10-29 16:19:24 -0500297 trigFunc->first,
Matthew Barthb6ebac82021-05-21 11:15:33 -0500298 trigFunc->second(jsonTrig, getName(), _actions));
Matthew Barth0620be72021-04-14 13:31:12 -0500299 }
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 }
Matthew Barth9f1632e2021-03-31 15:51:50 -0500314}
315
Matt Spinlerc3eb7b32022-04-25 15:44:16 -0500316json 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
Matthew Barth3174e722020-09-15 15:13:40 -0500337} // namespace phosphor::fan::control::json