xref: /openbmc/phosphor-fan-presence/control/json/manager.hpp (revision 4fa67aa12bd73f7d528225f0c3c93c3242bbcc23)
1  /**
2   * Copyright © 2022 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  #pragma once
17  
18  #include "action.hpp"
19  #include "event.hpp"
20  #include "group.hpp"
21  #include "json_config.hpp"
22  #include "power_state.hpp"
23  #include "profile.hpp"
24  #include "sdbusplus.hpp"
25  #include "utils/flight_recorder.hpp"
26  #include "zone.hpp"
27  
28  #include <nlohmann/json.hpp>
29  #include <phosphor-logging/log.hpp>
30  #include <sdbusplus/bus.hpp>
31  #include <sdbusplus/server/manager.hpp>
32  #include <sdeventplus/event.hpp>
33  #include <sdeventplus/source/event.hpp>
34  #include <sdeventplus/utility/timer.hpp>
35  
36  #include <chrono>
37  #include <format>
38  #include <map>
39  #include <memory>
40  #include <optional>
41  #include <tuple>
42  #include <utility>
43  #include <vector>
44  
45  namespace phosphor::fan::control::json
46  {
47  
48  using json = nlohmann::json;
49  using namespace phosphor::logging;
50  
51  /* Application name to be appended to the path for loading a JSON config file */
52  constexpr auto confAppName = "control";
53  
54  /* Type of timers supported */
55  enum class TimerType
56  {
57      oneshot,
58      repeating,
59  };
60  
61  /**
62   * Package of data required when a timer expires
63   * Tuple constructed of:
64   *      std::string = Timer package unique identifier
65   *      std::vector<std::unique_ptr<ActionBase>> = List of pointers to actions
66   * that run when the timer expires
67   *      const std::vector<Group> = List of groups
68   *      bool = If groups should be preloaded before actions are run
69   */
70  using TimerPkg =
71      std::tuple<std::string, std::vector<std::unique_ptr<ActionBase>>&,
72                 const std::vector<Group>&, bool>;
73  /**
74   * Data associated with a running timer that's used when it expires
75   * Pair constructed of:
76   *      TimerType = Type of timer to manage expired timer instances
77   *      TimerPkg = Package of data required when the timer expires
78   */
79  using TimerData = std::pair<TimerType, TimerPkg>;
80  /* Dbus event timer */
81  using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
82  
83  /* Dbus signal object */
84  constexpr auto Path = 0;
85  constexpr auto Intf = 1;
86  constexpr auto Prop = 2;
87  using SignalObject = std::tuple<std::string, std::string, std::string>;
88  /* Dbus signal actions */
89  using TriggerActions =
90      std::vector<std::reference_wrapper<std::unique_ptr<ActionBase>>>;
91  /**
92   * Signal handler function that handles parsing a signal's message for a
93   * particular signal object and stores the results in the manager
94   */
95  using SignalHandler =
96      std::function<bool(sdbusplus::message_t&, const SignalObject&, Manager&)>;
97  /**
98   * Package of data required when a signal is received
99   * Tuple constructed of:
100   *     SignalHandler = Signal handler function
101   *     SignalObject = Dbus signal object
102   *     TriggerActions = List of actions that are run when the signal is received
103   */
104  using SignalPkg = std::tuple<SignalHandler, SignalObject, TriggerActions>;
105  /**
106   * Data associated to a subscribed signal
107   * Tuple constructed of:
108   *     std::unique_ptr<std::vector<SignalPkg>> =
109   *         Pointer to list of the signal's packages
110   *     std::unique_ptr<sdbusplus::bus::match_t> =
111   *         Pointer to match holding the subscription to a signal
112   */
113  using SignalData = std::tuple<std::unique_ptr<std::vector<SignalPkg>>,
114                                std::unique_ptr<sdbusplus::bus::match_t>>;
115  
116  /**
117   * Package of data from a D-Bus call to get managed objects
118   * Tuple constructed of:
119   *     std::map<Path,            // D-Bus Path
120   *       std::map<Intf,          // D-Bus Interface
121   *         std::map<Property,    // D-Bus Property
122   *         std::variant>>>       // Variant value of that property
123   */
124  using Path_v = sdbusplus::message::object_path;
125  using Intf_v = std::string;
126  using Prop_v = std::string;
127  using ManagedObjects =
128      std::map<Path_v, std::map<Intf_v, std::map<Prop_v, PropertyVariantType>>>;
129  
130  /**
131   * Actions to run when a parameter trigger runs.
132   */
133  using ParamTriggerData = std::vector<
134      std::reference_wrapper<const std::vector<std::unique_ptr<ActionBase>>>>;
135  
136  /**
137   * @class Manager - Represents the fan control manager's configuration
138   *
139   * A fan control manager configuration is optional, therefore the "manager.json"
140   * file is also optional. The manager configuration is used to populate
141   * fan control's manager parameters which are used in how the application
142   * operates, not in how the fans are controlled.
143   *
144   * When no manager configuration exists, the fan control application starts,
145   * processes any configured events and then begins controlling fans according
146   * to those events.
147   */
148  class Manager
149  {
150    public:
151      Manager() = delete;
152      Manager(const Manager&) = delete;
153      Manager(Manager&&) = delete;
154      Manager& operator=(const Manager&) = delete;
155      Manager& operator=(Manager&&) = delete;
156      ~Manager() = default;
157  
158      /**
159       * Constructor
160       * Parses and populates the fan control manager attributes from a json file
161       *
162       * @param[in] event - sdeventplus event loop
163       */
164      explicit Manager(const sdeventplus::Event& event);
165  
166      /**
167       * @brief Callback function to handle receiving a HUP signal to reload the
168       * JSON configurations.
169       */
170      void sighupHandler(sdeventplus::source::Signal&,
171                         const struct signalfd_siginfo*);
172  
173      /**
174       * @brief Callback function to handle receiving a USR1 signal to dump
175       * the flight recorder.
176       */
177      void dumpDebugData(sdeventplus::source::Signal&,
178                         const struct signalfd_siginfo*);
179  
180      /**
181       * @brief Get the active profiles of the system where an empty list
182       * represents that only configuration entries without a profile defined will
183       * be loaded.
184       *
185       * @return - The list of active profiles
186       */
187      static const std::vector<std::string>& getActiveProfiles();
188  
189      /**
190       * @brief Load the configuration of a given JSON class object based on the
191       * active profiles
192       *
193       * @param[in] isOptional - JSON configuration file is optional or not
194       * @param[in] args - Arguments to be forwarded to each instance of `T`
195       *   (*Note that a sdbusplus bus object is required as the first argument)
196       *
197       * @return Map of configuration entries
198       *     Map of configuration keys to their corresponding configuration object
199       */
200      template <typename T, typename... Args>
getConfig(bool isOptional,Args &&...args)201      static std::map<configKey, std::unique_ptr<T>> getConfig(bool isOptional,
202                                                               Args&&... args)
203      {
204          std::map<configKey, std::unique_ptr<T>> config;
205  
206          auto confFile = fan::JsonConfig::getConfFile(
207              confAppName, T::confFileName, isOptional);
208  
209          if (!confFile.empty())
210          {
211              FlightRecorder::instance().log(
212                  "main", std::format("Loading configuration from {}",
213                                      confFile.string()));
214              for (const auto& entry : fan::JsonConfig::load(confFile))
215              {
216                  if (entry.contains("profiles"))
217                  {
218                      std::vector<std::string> profiles;
219                      for (const auto& profile : entry["profiles"])
220                      {
221                          profiles.emplace_back(
222                              profile.template get<std::string>());
223                      }
224                      // Do not create the object if its profiles are not in the
225                      // list of active profiles
226                      if (!profiles.empty() &&
227                          !std::any_of(profiles.begin(), profiles.end(),
228                                       [](const auto& name) {
229                                           return std::find(
230                                                      getActiveProfiles().begin(),
231                                                      getActiveProfiles().end(),
232                                                      name) !=
233                                                  getActiveProfiles().end();
234                                       }))
235                      {
236                          continue;
237                      }
238                  }
239                  auto obj =
240                      std::make_unique<T>(entry, std::forward<Args>(args)...);
241                  config.emplace(
242                      std::make_pair(obj->getName(), obj->getProfiles()),
243                      std::move(obj));
244              }
245              log<level::INFO>(
246                  std::format("Configuration({}) loaded successfully",
247                              T::confFileName)
248                      .c_str());
249              FlightRecorder::instance().log(
250                  "main", std::format("Configuration({}) loaded successfully",
251                                      T::confFileName));
252          }
253          return config;
254      }
255  
256      /**
257       * @brief Check if the given input configuration key matches with another
258       * configuration key that it's to be included in
259       *
260       * @param[in] input - Config key to be included in another config object
261       * @param[in] comp - Config key of the config object to compare with
262       *
263       * @return Whether the configuration object should be included
264       */
265      static bool inConfig(const configKey& input, const configKey& comp);
266  
267      /**
268       * @brief Check if the given path and inteface is owned by a dbus service
269       *
270       * @param[in] path - Dbus object path
271       * @param[in] intf - Dbus object interface
272       *
273       * @return - Whether the service has an owner for the given object path and
274       * interface
275       */
276      static bool hasOwner(const std::string& path, const std::string& intf);
277  
278      /**
279       * @brief Sets the dbus service owner state for all entries in the _servTree
280       * cache and removes associated objects from the _objects cache
281       *
282       * @param[in] serv - Dbus service name
283       * @param[in] hasOwner - Dbus service owner state
284       */
285      void setOwner(const std::string& serv, bool hasOwner);
286  
287      /**
288       * @brief Sets the dbus service owner state of a given object
289       *
290       * @param[in] path - Dbus object path
291       * @param[in] serv - Dbus service name
292       * @param[in] intf - Dbus object interface
293       * @param[in] isOwned - Dbus service owner state
294       */
295      void setOwner(const std::string& path, const std::string& serv,
296                    const std::string& intf, bool isOwned);
297  
298      /**
299       * @brief Add a set of services for a path and interface by retrieving all
300       * the path subtrees to the given depth from root for the interface
301       *
302       * @param[in] intf - Interface to add services for
303       * @param[in] depth - Depth of tree traversal from root path
304       *
305       * @throws - DBusMethodError
306       * Throws a DBusMethodError when the `getSubTree` method call fails
307       */
308      static void addServices(const std::string& intf, int32_t depth);
309  
310      /**
311       * @brief Get the service for a given path and interface from cached
312       * dataset and attempt to add all the services for the given path/interface
313       * when it's not found
314       *
315       * @param[in] path - Path to get service for
316       * @param[in] intf - Interface to get service for
317       *
318       * @return - The now cached service name
319       *
320       * @throws - DBusMethodError
321       * Ripples up a DBusMethodError exception from calling addServices
322       */
323      static const std::string& getService(const std::string& path,
324                                           const std::string& intf);
325  
326      /**
327       * @brief Get all the object paths for a given service and interface from
328       * the cached dataset and try to add all the services for the given
329       * interface when no paths are found and then attempt to get all the object
330       * paths again
331       *
332       * @param[in] serv - Service name to get paths for
333       * @param[in] intf - Interface to get paths for
334       *
335       * @return The cached object paths
336       */
337      std::vector<std::string> getPaths(const std::string& serv,
338                                        const std::string& intf);
339  
340      /**
341       * @brief Add objects to the cached dataset by first using
342       * `getManagedObjects` for the same service providing the given path and
343       * interface or just add the single object of the given path, interface, and
344       * property if that fails.
345       *
346       * @param[in] path - Dbus object's path
347       * @param[in] intf - Dbus object's interface
348       * @param[in] prop - Dbus object's property
349       *
350       * @throws - DBusMethodError
351       * Throws a DBusMethodError when the the service is failed to be found or
352       * when the `getManagedObjects` method call fails
353       */
addObjects(const std::string & path,const std::string & intf,const std::string & prop)354      void addObjects(const std::string& path, const std::string& intf,
355                      const std::string& prop)
356      {
357          addObjects(path, intf, prop, std::string{});
358      }
359  
360      /**
361       * @copydoc Manager::addObjects()
362       *
363       * If the service is known, then it can be used to add all objects
364       * in that service with the interface passed in to the cache instead of
365       * having to look it up.  This is done so objects can still be
366       * added even when the D-Bus path passed in doesn't exist so it
367       * can't be used to get a service name.
368       *
369       * @param[in] path - Dbus object's path
370       * @param[in] intf - Dbus object's interface
371       * @param[in] prop - Dbus object's property
372       * @param[in] serviceName - The service of the path/intf/prop if known
373       */
374      void addObjects(const std::string& path, const std::string& intf,
375                      const std::string& prop, const std::string& serviceName);
376  
377      /**
378       * @brief Get an object's property value
379       *
380       * @param[in] path - Dbus object's path
381       * @param[in] intf - Dbus object's interface
382       * @param[in] prop - Dbus object's property
383       */
384      const std::optional<PropertyVariantType> getProperty(
385          const std::string& path, const std::string& intf,
386          const std::string& prop);
387  
388      /**
389       * @brief Set/update an object's property value
390       *
391       * @param[in] path - Dbus object's path
392       * @param[in] intf - Dbus object's interface
393       * @param[in] prop - Dbus object's property
394       * @param[in] value - Dbus object's property value
395       */
396      void setProperty(const std::string& path, const std::string& intf,
397                       const std::string& prop, PropertyVariantType value);
398  
399      /**
400       * @brief Remove an object's interface
401       *
402       * @param[in] path - Dbus object's path
403       * @param[in] intf - Dbus object's interface
404       */
removeInterface(const std::string & path,const std::string & intf)405      inline void removeInterface(const std::string& path,
406                                  const std::string& intf)
407      {
408          auto itPath = _objects.find(path);
409          if (itPath != std::end(_objects))
410          {
411              _objects[path].erase(intf);
412          }
413      }
414  
415      /**
416       * @brief Get the object's property value as a variant
417       *
418       * @param[in] path - Path of the object containing the property
419       * @param[in] intf - Interface name containing the property
420       * @param[in] prop - Name of property
421       *
422       * @return - The object's property value as a variant
423       */
getObjValueVariant(const std::string & path,const std::string & intf,const std::string & prop)424      static inline auto getObjValueVariant(const std::string& path,
425                                            const std::string& intf,
426                                            const std::string& prop)
427      {
428          return _objects.at(path).at(intf).at(prop);
429      };
430  
431      /**
432       * @brief Add a dbus timer
433       *
434       * @param[in] type - Type of timer
435       * @param[in] interval - Timer interval in microseconds
436       * @param[in] pkg - Packaged data for when timer expires
437       */
438      void addTimer(const TimerType type,
439                    const std::chrono::microseconds interval,
440                    std::unique_ptr<TimerPkg> pkg);
441  
442      /**
443       * @brief Callback when a timer expires
444       *
445       * @param[in] data - Data to be used when the timer expired
446       */
447      void timerExpired(TimerData& data);
448  
449      /**
450       * @brief Get the signal data for a given match string
451       *
452       * @param[in] sigMatch - Signal match string
453       *
454       * @return - Reference to the signal data for the given match string
455       */
getSignal(const std::string & sigMatch)456      std::vector<SignalData>& getSignal(const std::string& sigMatch)
457      {
458          return _signals[sigMatch];
459      }
460  
461      /**
462       * @brief Handle receiving signals
463       *
464       * @param[in] msg - Signal message containing the signal's data
465       * @param[in] pkgs - Signal packages associated to the signal being handled
466       */
467      void handleSignal(sdbusplus::message_t& msg,
468                        const std::vector<SignalPkg>* pkgs);
469  
470      /**
471       * @brief Get the sdbusplus bus object
472       */
getBus()473      inline auto& getBus()
474      {
475          return _bus;
476      }
477  
478      /**
479       * @brief Is the power state on
480       *
481       * @return Current power state of the system
482       */
isPowerOn() const483      inline bool isPowerOn() const
484      {
485          return _powerState->isPowerOn();
486      }
487  
488      /**
489       * @brief Load all the fan control JSON configuration files
490       *
491       * This is where all the fan control JSON configuration files are parsed and
492       * loaded into their associated objects. Anything that needs to be done when
493       * the Manager object is constructed or handling a SIGHUP to reload the
494       * configurations needs to be done here.
495       */
496      void load();
497  
498      /**
499       * @brief Sets a value in the parameter map.
500       *
501       * If it's a std::nullopt, it will be deleted instead.
502       *
503       * @param[in] name - The parameter name
504       * @param[in] value - The parameter value
505       */
setParameter(const std::string & name,const std::optional<PropertyVariantType> & value)506      static void setParameter(const std::string& name,
507                               const std::optional<PropertyVariantType>& value)
508      {
509          if (value)
510          {
511              auto it = _parameters.find(name);
512              auto changed = (it == _parameters.end()) ||
513                             ((it != _parameters.end()) && it->second != *value);
514              _parameters[name] = *value;
515  
516              if (changed)
517              {
518                  runParameterActions(name);
519              }
520          }
521          else
522          {
523              size_t deleted = _parameters.erase(name);
524  
525              if (deleted)
526              {
527                  runParameterActions(name);
528              }
529          }
530      }
531  
532      /**
533       * @brief Returns a value from the parameter map
534       *
535       * @param[in] name - The parameter name
536       *
537       * @return The parameter value, or std::nullopt if not found
538       */
getParameter(const std::string & name)539      static std::optional<PropertyVariantType> getParameter(
540          const std::string& name)
541      {
542          auto it = _parameters.find(name);
543          if (it != _parameters.end())
544          {
545              return it->second;
546          }
547  
548          return std::nullopt;
549      }
550  
551      /**
552       * @brief Runs the actions registered to a parameter
553       *        trigger with this name.
554       *
555       * @param[in] name - The parameter name
556       */
557      static void runParameterActions(const std::string& name);
558  
559      /**
560       * @brief Adds a parameter trigger
561       *
562       * @param[in] name - The parameter name
563       * @param[in] actions - The actions to run on the trigger
564       */
565      static void addParameterTrigger(
566          const std::string& name,
567          std::vector<std::unique_ptr<ActionBase>>& actions);
568  
569      /* The name of the dump file */
570      static const std::string dumpFile;
571  
572    private:
573      /**
574       * @brief Helper to detect when a property's double contains a NaN
575       * (not-a-number) value.
576       *
577       * @param[in] value - The property to test
578       */
PropertyContainsNan(const PropertyVariantType & value)579      static bool PropertyContainsNan(const PropertyVariantType& value)
580      {
581          return (std::holds_alternative<double>(value) &&
582                  std::isnan(std::get<double>(value)));
583      }
584  
585      /**
586       * @brief Insert managed objects into cache, but filter out properties
587       * containing unwanted NaN (not-a-number) values and properties that
588       * are on D-Bus paths that aren't in an existing Group object.
589       *
590       * @param[in] ref - The map of ManagedObjects to insert into cache
591       */
592      void insertFilteredObjects(ManagedObjects& ref);
593  
594      /* The sdbusplus bus object to use */
595      sdbusplus::bus_t& _bus;
596  
597      /* The sdeventplus even loop to use */
598      sdeventplus::Event _event;
599  
600      /* The sdbusplus manager object to set the ObjectManager interface */
601      sdbusplus::server::manager_t _mgr;
602  
603      /* Whether loading the config files is allowed or not */
604      bool _loadAllowed;
605  
606      /* The system's power state determination object */
607      std::unique_ptr<PowerState> _powerState;
608  
609      /* List of profiles configured */
610      std::map<configKey, std::unique_ptr<Profile>> _profiles;
611  
612      /* List of active profiles */
613      static std::vector<std::string> _activeProfiles;
614  
615      /* Subtree map of paths to services of interfaces(with ownership state) */
616      static std::map<
617          std::string,
618          std::map<std::string, std::pair<bool, std::vector<std::string>>>>
619          _servTree;
620  
621      /* Object map of paths to interfaces of properties and their values */
622      static std::map<
623          std::string,
624          std::map<std::string, std::map<std::string, PropertyVariantType>>>
625          _objects;
626  
627      /* List of timers and their data to be processed when expired */
628      std::vector<std::pair<std::unique_ptr<TimerData>, Timer>> _timers;
629  
630      /* Map of signal match strings to a list of signal handler data */
631      std::unordered_map<std::string, std::vector<SignalData>> _signals;
632  
633      /* List of zones configured */
634      std::map<configKey, std::unique_ptr<Zone>> _zones;
635  
636      /* List of events configured */
637      std::map<configKey, std::unique_ptr<Event>> _events;
638  
639      /**
640       * @brief A map of parameter names and values that are something
641       *        other than just D-Bus property values that other actions
642       *        can set and use.
643       */
644      static std::unordered_map<std::string, PropertyVariantType> _parameters;
645  
646      /**
647       * @brief Map of parameter names to the actions to run when their
648       *        values change.
649       */
650      static std::unordered_map<std::string, TriggerActions> _parameterTriggers;
651  
652      /**
653       * @brief Callback for power state changes
654       *
655       * @param[in] powerStateOn - Whether the power state is on or not
656       *
657       * Callback function bound to the PowerState object instance to handle each
658       * time the power state changes.
659       */
660      void powerStateChanged(bool powerStateOn);
661  
662      /**
663       * @brief Find the service name for a given path and interface from the
664       * cached dataset
665       *
666       * @param[in] path - Path to get service for
667       * @param[in] intf - Interface to get service for
668       *
669       * @return - The cached service name
670       */
671      static const std::string& findService(const std::string& path,
672                                            const std::string& intf);
673  
674      /**
675       * @brief Find all the paths for a given service and interface from the
676       * cached dataset
677       *
678       * @param[in] serv - Service name to get paths for
679       * @param[in] intf - Interface to get paths for
680       *
681       * @return - The cached object paths
682       */
683      std::vector<std::string> findPaths(const std::string& serv,
684                                         const std::string& intf);
685  
686      /**
687       * @brief Parse and set the configured profiles from the profiles JSON file
688       *
689       * Retrieves the optional profiles JSON configuration file, parses it, and
690       * creates a list of configured profiles available to the other
691       * configuration files. These profiles can be used to remove or include
692       * entries within the other configuration files.
693       */
694      void setProfiles();
695  
696      /**
697       * @brief Dump the _objects, _servTree, and _parameters maps to JSON
698       *
699       * @param[out] data - The JSON that will be filled in
700       */
701      void dumpCache(json& data);
702  
703      /**
704       * @brief Add a list of groups to the cache dataset.
705       *
706       * @param[in] groups - The groups to add
707       */
708      void addGroups(const std::vector<Group>& groups);
709  };
710  
711  } // namespace phosphor::fan::control::json
712