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