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