xref: /openbmc/phosphor-fan-presence/control/json/triggers/signal.cpp (revision 64b5ac203518568ec8b7569d0e785352278f2472)
1 /**
2  * Copyright © 2021 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 "signal.hpp"
17 
18 #include "../manager.hpp"
19 #include "action.hpp"
20 #include "group.hpp"
21 #include "handlers.hpp"
22 #include "trigger_aliases.hpp"
23 
24 #include <nlohmann/json.hpp>
25 #include <phosphor-logging/lg2.hpp>
26 #include <sdbusplus/bus/match.hpp>
27 
28 #include <algorithm>
29 #include <functional>
30 #include <iterator>
31 #include <memory>
32 #include <numeric>
33 #include <utility>
34 #include <vector>
35 
36 namespace phosphor::fan::control::json::trigger::signal
37 {
38 
39 using json = nlohmann::json;
40 using namespace sdbusplus::bus::match;
41 
subscribe(const std::string & match,SignalPkg && signalPkg,std::function<bool (SignalPkg &)> isSameSig,Manager * mgr)42 void subscribe(const std::string& match, SignalPkg&& signalPkg,
43                std::function<bool(SignalPkg&)> isSameSig, Manager* mgr)
44 {
45     auto& signalData = mgr->getSignal(match);
46     if (signalData.empty())
47     {
48         // Signal subscription doesnt exist, add signal package and subscribe
49         std::unique_ptr<std::vector<SignalPkg>> pkgs =
50             std::make_unique<std::vector<SignalPkg>>();
51         pkgs->emplace_back(std::move(signalPkg));
52         std::unique_ptr<sdbusplus::bus::match_t> ptrMatch = nullptr;
53         if (!match.empty())
54         {
55             // Subscribe to signal
56             ptrMatch = std::make_unique<sdbusplus::bus::match_t>(
57                 mgr->getBus(), match.c_str(),
58                 std::bind(std::mem_fn(&Manager::handleSignal), &(*mgr),
59                           std::placeholders::_1, pkgs.get()));
60         }
61         signalData.emplace_back(std::move(pkgs), std::move(ptrMatch));
62     }
63     else
64     {
65         // Signal subscription already exists
66         // Only a single signal data entry tied to each match is supported
67         auto& pkgs = std::get<std::unique_ptr<std::vector<SignalPkg>>>(
68             signalData.front());
69         auto sameSignal = false;
70         for (auto& pkg : *pkgs)
71         {
72             if (isSameSig(pkg))
73             {
74                 // Same SignalObject signal to trigger event actions,
75                 // add actions to be run when signal for SignalObject received
76                 auto& pkgActions = std::get<TriggerActions>(signalPkg);
77                 auto& actions = std::get<TriggerActions>(pkg);
78                 actions.insert(actions.end(), pkgActions.begin(),
79                                pkgActions.end());
80                 sameSignal = true;
81                 break;
82             }
83         }
84         if (!sameSignal)
85         {
86             // Expected signal differs, add signal package
87             pkgs->emplace_back(std::move(signalPkg));
88         }
89     }
90 }
91 
propertiesChanged(Manager * mgr,const Group & group,TriggerActions & actions,const json &)92 void propertiesChanged(Manager* mgr, const Group& group,
93                        TriggerActions& actions, const json&)
94 {
95     // Groups are optional, but a signal triggered event with no groups
96     // will do nothing since signals require a group
97     for (const auto& member : group.getMembers())
98     {
99         // Setup property changed signal handler on the group member's
100         // property
101         const auto match =
102             rules::propertiesChanged(member, group.getInterface());
103         SignalPkg signalPkg = {
104             Handlers::propertiesChanged,
105             SignalObject(std::cref(member), std::cref(group.getInterface()),
106                          std::cref(group.getProperty())),
107             actions};
108         auto isSameSig = [&prop = group.getProperty()](SignalPkg& pkg) {
109             auto& obj = std::get<SignalObject>(pkg);
110             return prop == std::get<Prop>(obj);
111         };
112 
113         subscribe(match, std::move(signalPkg), isSameSig, mgr);
114     }
115 }
116 
interfacesAdded(Manager * mgr,const Group & group,TriggerActions & actions,const json &)117 void interfacesAdded(Manager* mgr, const Group& group, TriggerActions& actions,
118                      const json&)
119 {
120     // Groups are optional, but a signal triggered event with no groups
121     // will do nothing since signals require a group
122     for (const auto& member : group.getMembers())
123     {
124         // Setup interfaces added signal handler on the group member
125         const auto match =
126             rules::interfacesAdded() + rules::argNpath(0, member);
127         SignalPkg signalPkg = {
128             Handlers::interfacesAdded,
129             SignalObject(std::cref(member), std::cref(group.getInterface()),
130                          std::cref(group.getProperty())),
131             actions};
132         auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) {
133             auto& obj = std::get<SignalObject>(pkg);
134             return intf == std::get<Intf>(obj);
135         };
136 
137         subscribe(match, std::move(signalPkg), isSameSig, mgr);
138     }
139 }
140 
interfacesRemoved(Manager * mgr,const Group & group,TriggerActions & actions,const json &)141 void interfacesRemoved(Manager* mgr, const Group& group,
142                        TriggerActions& actions, const json&)
143 {
144     // Groups are optional, but a signal triggered event with no groups
145     // will do nothing since signals require a group
146     for (const auto& member : group.getMembers())
147     {
148         // Setup interfaces removed signal handler on the group member
149         const auto match =
150             rules::interfacesRemoved() + rules::argNpath(0, member);
151         SignalPkg signalPkg = {
152             Handlers::interfacesRemoved,
153             SignalObject(std::cref(member), std::cref(group.getInterface()),
154                          std::cref(group.getProperty())),
155             actions};
156         auto isSameSig = [&intf = group.getInterface()](SignalPkg& pkg) {
157             auto& obj = std::get<SignalObject>(pkg);
158             return intf == std::get<Intf>(obj);
159         };
160 
161         subscribe(match, std::move(signalPkg), isSameSig, mgr);
162     }
163 }
164 
nameOwnerChanged(Manager * mgr,const Group & group,TriggerActions & actions,const json &)165 void nameOwnerChanged(Manager* mgr, const Group& group, TriggerActions& actions,
166                       const json&)
167 {
168     std::vector<std::string> grpServices;
169     // Groups are optional, but a signal triggered event with no groups
170     // will do nothing since signals require a group
171     for (const auto& member : group.getMembers())
172     {
173         auto serv = group.getService();
174         if (serv.empty())
175         {
176             serv = Manager::getService(member, group.getInterface());
177         }
178         if (!serv.empty())
179         {
180             // No need to re-subscribe to the same service's nameOwnerChanged
181             // signal when a prior group member provided by the same service
182             // already did the subscription
183             if (std::find(grpServices.begin(), grpServices.end(), serv) ==
184                 grpServices.end())
185             {
186                 // Setup name owner changed signal handler on the group
187                 // member's service
188                 const auto match = rules::nameOwnerChanged(serv);
189                 SignalPkg signalPkg = {Handlers::nameOwnerChanged,
190                                        SignalObject(), actions};
191                 // If signal match already exists, then the service will be the
192                 // same so add action to be run
193                 auto isSameSig = [](SignalPkg&) { return true; };
194 
195                 subscribe(match, std::move(signalPkg), isSameSig, mgr);
196                 grpServices.emplace_back(serv);
197             }
198         }
199         else
200         {
201             // Unable to construct nameOwnerChanged match string
202             // Path and/or interface configured does not exist on dbus yet?
203             // TODO How to handle this? Create timer to keep checking for
204             // service to appear? When to stop checking?
205             lg2::error(
206                 "Events will not be triggered by name owner changed"
207                 "signals from service of path {MEMBER}, interface {GROUP_INTERFACE}",
208                 "MEMBER", member, "GROUP_INTERFACE", group.getInterface());
209         }
210     }
211 }
212 
member(Manager * mgr,const Group & group,TriggerActions & actions,const json &)213 void member(Manager* mgr, const Group& group, TriggerActions& actions,
214             const json&)
215 {
216     // No SignalObject required to associate to this signal
217     SignalPkg signalPkg = {Handlers::member, SignalObject(), actions};
218     // If signal match already exists, then the member signal will be the
219     // same so add action to be run
220     auto isSameSig = [](SignalPkg&) { return true; };
221 
222     // Groups are optional, but a signal triggered event with no groups
223     // will do nothing since signals require a group
224     for (const auto& member : group.getMembers())
225     {
226         // Subscribe for signal from each group member
227         const auto match =
228             rules::type::signal() + rules::member(group.getProperty()) +
229             rules::path(member) + rules::interface(group.getInterface());
230 
231         subscribe(match, std::move(signalPkg), isSameSig, mgr);
232     }
233 }
234 
triggerSignal(const json & jsonObj,const std::string & eventName,std::vector<std::unique_ptr<ActionBase>> &)235 enableTrigger triggerSignal(
236     const json& jsonObj, const std::string& eventName,
237     std::vector<std::unique_ptr<ActionBase>>& /*actions*/)
238 {
239     auto subscriber = signals.end();
240     if (jsonObj.contains("signal"))
241     {
242         auto signal = jsonObj["signal"].get<std::string>();
243         std::transform(signal.begin(), signal.end(), signal.begin(), tolower);
244         subscriber = signals.find(signal);
245     }
246     if (subscriber == signals.end())
247     {
248         // Construct list of available signals
249         auto availSignals =
250             std::accumulate(std::next(signals.begin()), signals.end(),
251                             signals.begin()->first, [](auto list, auto signal) {
252                                 return std::move(list) + ", " + signal.first;
253                             });
254         lg2::error(
255             "Event '{EVENT_NAME}' requires a supported signal given to be "
256             "triggered by signal, available signals: {AVAILABLE_SIGNALS}",
257             "EVENT_NAME", eventName, "AVAILABLE_SIGNALS", availSignals);
258         throw std::runtime_error(
259             "Event requires a supported signal given to be triggered by signal.");
260     }
261 
262     return [subscriber = std::move(subscriber),
263             jsonObj](const std::string& /*eventName*/, Manager* mgr,
264                      const std::vector<Group>& groups,
265                      std::vector<std::unique_ptr<ActionBase>>& actions) {
266         TriggerActions signalActions;
267         std::for_each(actions.begin(), actions.end(),
268                       [&signalActions](auto& action) {
269                           signalActions.emplace_back(std::ref(action));
270                       });
271         for (const auto& group : groups)
272         {
273             // Call signal subscriber for each group
274             subscriber->second(mgr, group, signalActions, jsonObj);
275         }
276     };
277 }
278 
279 } // namespace phosphor::fan::control::json::trigger::signal
280