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