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