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