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