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