xref: /openbmc/phosphor-fan-presence/control/json/manager.cpp (revision c024d780229822fe4eb5a948b4e5d76a8e28f3f9)
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 #include "config.h"
17 
18 #include "manager.hpp"
19 
20 #include "../utils/flight_recorder.hpp"
21 #include "action.hpp"
22 #include "event.hpp"
23 #include "fan.hpp"
24 #include "group.hpp"
25 #include "json_config.hpp"
26 #include "power_state.hpp"
27 #include "profile.hpp"
28 #include "sdbusplus.hpp"
29 #include "zone.hpp"
30 
31 #include <systemd/sd-bus.h>
32 
33 #include <nlohmann/json.hpp>
34 #include <sdbusplus/bus.hpp>
35 #include <sdbusplus/server/manager.hpp>
36 #include <sdeventplus/event.hpp>
37 #include <sdeventplus/utility/timer.hpp>
38 
39 #include <algorithm>
40 #include <chrono>
41 #include <filesystem>
42 #include <functional>
43 #include <map>
44 #include <memory>
45 #include <tuple>
46 #include <utility>
47 #include <vector>
48 
49 namespace phosphor::fan::control::json
50 {
51 
52 using json = nlohmann::json;
53 
54 std::vector<std::string> Manager::_activeProfiles;
55 std::map<std::string,
56          std::map<std::string, std::pair<bool, std::vector<std::string>>>>
57     Manager::_servTree;
58 std::map<std::string,
59          std::map<std::string, std::map<std::string, PropertyVariantType>>>
60     Manager::_objects;
61 std::unordered_map<std::string, PropertyVariantType> Manager::_parameters;
62 
63 const std::string Manager::dumpFile = "/tmp/fan_control_dump.json";
64 
65 Manager::Manager(const sdeventplus::Event& event) :
66     _bus(util::SDBusPlus::getBus()), _event(event),
67     _mgr(util::SDBusPlus::getBus(), CONTROL_OBJPATH), _loadAllowed(true),
68     _powerState(std::make_unique<PGoodState>(
69         util::SDBusPlus::getBus(),
70         std::bind(std::mem_fn(&Manager::powerStateChanged), this,
71                   std::placeholders::_1)))
72 {}
73 
74 void Manager::sighupHandler(sdeventplus::source::Signal&,
75                             const struct signalfd_siginfo*)
76 {
77     // Save current set of available and active profiles
78     std::map<configKey, std::unique_ptr<Profile>> profiles;
79     profiles.swap(_profiles);
80     std::vector<std::string> activeProfiles;
81     activeProfiles.swap(_activeProfiles);
82 
83     try
84     {
85         _loadAllowed = true;
86         load();
87     }
88     catch (const std::runtime_error& re)
89     {
90         // Restore saved available and active profiles
91         _loadAllowed = false;
92         _profiles.swap(profiles);
93         _activeProfiles.swap(activeProfiles);
94         log<level::ERR>("Error reloading configs, no changes made",
95                         entry("LOAD_ERROR=%s", re.what()));
96     }
97 }
98 
99 void Manager::sigUsr1Handler(sdeventplus::source::Signal&,
100                              const struct signalfd_siginfo*)
101 {
102     debugDumpEventSource = std::make_unique<sdeventplus::source::Defer>(
103         _event, std::bind(std::mem_fn(&Manager::dumpDebugData), this,
104                           std::placeholders::_1));
105 }
106 
107 void Manager::dumpDebugData(sdeventplus::source::EventBase& /*source*/)
108 {
109     json data;
110     FlightRecorder::instance().dump(data);
111     dumpCache(data);
112 
113     std::for_each(_zones.begin(), _zones.end(), [&data](const auto& zone) {
114         data["zones"][zone.second->getName()] = zone.second->dump();
115     });
116 
117     std::ofstream file{Manager::dumpFile};
118     if (!file)
119     {
120         log<level::ERR>("Could not open file for fan dump");
121         return;
122     }
123 
124     file << std::setw(4) << data;
125 
126     debugDumpEventSource.reset();
127 }
128 
129 void Manager::dumpCache(json& data)
130 {
131     auto& objects = data["objects"];
132     for (const auto& [path, interfaces] : _objects)
133     {
134         auto& interfaceJSON = objects[path];
135 
136         for (const auto& [interface, properties] : interfaces)
137         {
138             auto& propertyJSON = interfaceJSON[interface];
139             for (const auto& [propName, propValue] : properties)
140             {
141                 std::visit(
142                     [&obj = propertyJSON[propName]](auto&& val) { obj = val; },
143                     propValue);
144             }
145         }
146     }
147 
148     auto& parameters = data["parameters"];
149     for (const auto& [name, value] : _parameters)
150     {
151         std::visit([&obj = parameters[name]](auto&& val) { obj = val; }, value);
152     }
153 
154     data["services"] = _servTree;
155 }
156 
157 void Manager::load()
158 {
159     if (_loadAllowed)
160     {
161         // Load the available profiles and which are active
162         setProfiles();
163 
164         // Load the zone configurations
165         auto zones = getConfig<Zone>(false, _event, this);
166         // Load the fan configurations and move each fan into its zone
167         auto fans = getConfig<Fan>(false);
168         for (auto& fan : fans)
169         {
170             configKey fanProfile =
171                 std::make_pair(fan.second->getZone(), fan.first.second);
172             auto itZone = std::find_if(
173                 zones.begin(), zones.end(), [&fanProfile](const auto& zone) {
174                     return Manager::inConfig(fanProfile, zone.first);
175                 });
176             if (itZone != zones.end())
177             {
178                 if (itZone->second->getTarget() != fan.second->getTarget() &&
179                     fan.second->getTarget() != 0)
180                 {
181                     // Update zone target to current target of the fan in the
182                     // zone
183                     itZone->second->setTarget(fan.second->getTarget());
184                 }
185                 itZone->second->addFan(std::move(fan.second));
186             }
187         }
188 
189         // Save all currently available groups, if any, then clear for reloading
190         auto groups = std::move(Event::getAllGroups(false));
191         Event::clearAllGroups();
192 
193         std::map<configKey, std::unique_ptr<Event>> events;
194         try
195         {
196             // Load any events configured, including all the groups
197             events = getConfig<Event>(true, this, zones);
198         }
199         catch (const std::runtime_error& re)
200         {
201             // Restore saved set of all available groups for current events
202             Event::setAllGroups(std::move(groups));
203             throw re;
204         }
205 
206         // Enable zones
207         _zones = std::move(zones);
208         std::for_each(_zones.begin(), _zones.end(),
209                       [](const auto& entry) { entry.second->enable(); });
210 
211         // Clear current timers and signal subscriptions before enabling events
212         // To save reloading services and/or objects into cache, do not clear
213         // cache
214         _timers.clear();
215         _signals.clear();
216 
217         // Enable events
218         _events = std::move(events);
219         std::for_each(_events.begin(), _events.end(),
220                       [](const auto& entry) { entry.second->enable(); });
221 
222         _loadAllowed = false;
223     }
224 }
225 
226 void Manager::powerStateChanged(bool powerStateOn)
227 {
228     if (powerStateOn)
229     {
230         if (_zones.empty())
231         {
232             throw std::runtime_error("No configured zones found at poweron");
233         }
234         std::for_each(_zones.begin(), _zones.end(), [](const auto& entry) {
235             entry.second->setTarget(entry.second->getPoweronTarget());
236         });
237 
238         // Tell events to run their power on triggers
239         std::for_each(_events.begin(), _events.end(),
240                       [](const auto& entry) { entry.second->powerOn(); });
241     }
242     else
243     {
244         // Tell events to run their power off triggers
245         std::for_each(_events.begin(), _events.end(),
246                       [](const auto& entry) { entry.second->powerOff(); });
247     }
248 }
249 
250 const std::vector<std::string>& Manager::getActiveProfiles()
251 {
252     return _activeProfiles;
253 }
254 
255 bool Manager::inConfig(const configKey& input, const configKey& comp)
256 {
257     // Config names dont match, do not include in config
258     if (input.first != comp.first)
259     {
260         return false;
261     }
262     // No profiles specified by input config, can be used in any config
263     if (input.second.empty())
264     {
265         return true;
266     }
267     else
268     {
269         // Profiles must have one match in the other's profiles(and they must be
270         // an active profile) to be used in the config
271         return std::any_of(
272             input.second.begin(), input.second.end(),
273             [&comp](const auto& lProfile) {
274                 return std::any_of(
275                     comp.second.begin(), comp.second.end(),
276                     [&lProfile](const auto& rProfile) {
277                         if (lProfile != rProfile)
278                         {
279                             return false;
280                         }
281                         auto activeProfs = getActiveProfiles();
282                         return std::find(activeProfs.begin(), activeProfs.end(),
283                                          lProfile) != activeProfs.end();
284                     });
285             });
286     }
287 }
288 
289 bool Manager::hasOwner(const std::string& path, const std::string& intf)
290 {
291     auto itServ = _servTree.find(path);
292     if (itServ == _servTree.end())
293     {
294         // Path not found in cache, therefore owner missing
295         return false;
296     }
297     for (const auto& service : itServ->second)
298     {
299         auto itIntf = std::find_if(
300             service.second.second.begin(), service.second.second.end(),
301             [&intf](const auto& interface) { return intf == interface; });
302         if (itIntf != std::end(service.second.second))
303         {
304             // Service found, return owner state
305             return service.second.first;
306         }
307     }
308     // Interface not found in cache, therefore owner missing
309     return false;
310 }
311 
312 void Manager::setOwner(const std::string& path, const std::string& serv,
313                        const std::string& intf, bool isOwned)
314 {
315     // Set owner state for specific object given
316     auto& ownIntf = _servTree[path][serv];
317     ownIntf.first = isOwned;
318     auto itIntf = std::find_if(
319         ownIntf.second.begin(), ownIntf.second.end(),
320         [&intf](const auto& interface) { return intf == interface; });
321     if (itIntf == std::end(ownIntf.second))
322     {
323         ownIntf.second.emplace_back(intf);
324     }
325 
326     // Update owner state on all entries of the same `serv` & `intf`
327     for (auto& itPath : _servTree)
328     {
329         if (itPath.first == path)
330         {
331             // Already set/updated owner on this path for `serv` & `intf`
332             continue;
333         }
334         for (auto& itServ : itPath.second)
335         {
336             if (itServ.first != serv)
337             {
338                 continue;
339             }
340             auto itIntf = std::find_if(
341                 itServ.second.second.begin(), itServ.second.second.end(),
342                 [&intf](const auto& interface) { return intf == interface; });
343             if (itIntf != std::end(itServ.second.second))
344             {
345                 itServ.second.first = isOwned;
346             }
347         }
348     }
349 }
350 
351 const std::string& Manager::findService(const std::string& path,
352                                         const std::string& intf)
353 {
354     static const std::string empty = "";
355 
356     auto itServ = _servTree.find(path);
357     if (itServ != _servTree.end())
358     {
359         for (const auto& service : itServ->second)
360         {
361             auto itIntf = std::find_if(
362                 service.second.second.begin(), service.second.second.end(),
363                 [&intf](const auto& interface) { return intf == interface; });
364             if (itIntf != std::end(service.second.second))
365             {
366                 // Service found, return service name
367                 return service.first;
368             }
369         }
370     }
371 
372     return empty;
373 }
374 
375 void Manager::addServices(const std::string& intf, int32_t depth)
376 {
377     // Get all subtree objects for the given interface
378     auto objects = util::SDBusPlus::getSubTreeRaw(util::SDBusPlus::getBus(),
379                                                   "/", intf, depth);
380     // Add what's returned to the cache of path->services
381     for (auto& itPath : objects)
382     {
383         auto pathIter = _servTree.find(itPath.first);
384         if (pathIter != _servTree.end())
385         {
386             // Path found in cache
387             for (auto& itServ : itPath.second)
388             {
389                 auto servIter = pathIter->second.find(itServ.first);
390                 if (servIter != pathIter->second.end())
391                 {
392                     // Service found in cache
393                     for (auto& itIntf : itServ.second)
394                     {
395                         if (std::find(servIter->second.second.begin(),
396                                       servIter->second.second.end(),
397                                       itIntf) == servIter->second.second.end())
398                         {
399                             // Add interface to cache
400                             servIter->second.second.emplace_back(itIntf);
401                         }
402                     }
403                 }
404                 else
405                 {
406                     // Service not found in cache
407                     auto intfs = {intf};
408                     pathIter->second[itServ.first] =
409                         std::make_pair(true, intfs);
410                 }
411             }
412         }
413         else
414         {
415             // Path not found in cache
416             auto intfs = {intf};
417             for (const auto& [servName, servIntfs] : itPath.second)
418             {
419                 _servTree[itPath.first][servName] = std::make_pair(true, intfs);
420             }
421         }
422     }
423 }
424 
425 const std::string& Manager::getService(const std::string& path,
426                                        const std::string& intf)
427 {
428     // Retrieve service from cache
429     const auto& serviceName = findService(path, intf);
430     if (serviceName.empty())
431     {
432         addServices(intf, 0);
433         return findService(path, intf);
434     }
435 
436     return serviceName;
437 }
438 
439 std::vector<std::string> Manager::findPaths(const std::string& serv,
440                                             const std::string& intf)
441 {
442     std::vector<std::string> paths;
443 
444     for (const auto& path : _servTree)
445     {
446         auto itServ = path.second.find(serv);
447         if (itServ != path.second.end())
448         {
449             if (std::find(itServ->second.second.begin(),
450                           itServ->second.second.end(),
451                           intf) != itServ->second.second.end())
452             {
453                 if (std::find(paths.begin(), paths.end(), path.first) ==
454                     paths.end())
455                 {
456                     paths.push_back(path.first);
457                 }
458             }
459         }
460     }
461 
462     return paths;
463 }
464 
465 std::vector<std::string> Manager::getPaths(const std::string& serv,
466                                            const std::string& intf)
467 {
468     auto paths = findPaths(serv, intf);
469     if (paths.empty())
470     {
471         addServices(intf, 0);
472         return findPaths(serv, intf);
473     }
474 
475     return paths;
476 }
477 
478 void Manager::addObjects(const std::string& path, const std::string& intf,
479                          const std::string& prop)
480 {
481     auto service = getService(path, intf);
482     if (service.empty())
483     {
484         // Log service not found for object
485         log<level::DEBUG>(
486             fmt::format("Unable to get service name for path {}, interface {}",
487                         path, intf)
488                 .c_str());
489         return;
490     }
491 
492     auto objMgrPaths = getPaths(service, "org.freedesktop.DBus.ObjectManager");
493     if (objMgrPaths.empty())
494     {
495         // No object manager interface provided by service?
496         // Attempt to retrieve property directly
497         auto variant = util::SDBusPlus::getPropertyVariant<PropertyVariantType>(
498             _bus, service, path, intf, prop);
499         _objects[path][intf][prop] = variant;
500         return;
501     }
502 
503     for (const auto& objMgrPath : objMgrPaths)
504     {
505         // Get all managed objects of service
506         auto objects = util::SDBusPlus::getManagedObjects<PropertyVariantType>(
507             _bus, service, objMgrPath);
508 
509         // Add what's returned to the cache of objects
510         for (auto& object : objects)
511         {
512             auto itPath = _objects.find(object.first);
513             if (itPath != _objects.end())
514             {
515                 // Path found in cache
516                 for (auto& interface : itPath->second)
517                 {
518                     auto itIntf = itPath->second.find(interface.first);
519                     if (itIntf != itPath->second.end())
520                     {
521                         // Interface found in cache
522                         for (auto& property : itIntf->second)
523                         {
524                             auto itProp = itIntf->second.find(property.first);
525                             if (itProp != itIntf->second.end())
526                             {
527                                 // Property found, update value
528                                 itProp->second = property.second;
529                             }
530                             else
531                             {
532                                 itIntf->second.insert(property);
533                             }
534                         }
535                     }
536                     else
537                     {
538                         // Interface not found in cache
539                         itPath->second.insert(interface);
540                     }
541                 }
542             }
543             else
544             {
545                 // Path not found in cache
546                 _objects.insert(object);
547             }
548         }
549     }
550 }
551 
552 const std::optional<PropertyVariantType>
553     Manager::getProperty(const std::string& path, const std::string& intf,
554                          const std::string& prop)
555 {
556     // TODO Objects hosted by fan control (i.e. ThermalMode) are required to
557     // update the cache upon being set/updated
558     auto itPath = _objects.find(path);
559     if (itPath != _objects.end())
560     {
561         auto itIntf = itPath->second.find(intf);
562         if (itIntf != itPath->second.end())
563         {
564             auto itProp = itIntf->second.find(prop);
565             if (itProp != itIntf->second.end())
566             {
567                 return itProp->second;
568             }
569         }
570     }
571 
572     return std::nullopt;
573 }
574 
575 void Manager::addTimer(const TimerType type,
576                        const std::chrono::microseconds interval,
577                        std::unique_ptr<TimerPkg> pkg)
578 {
579     auto dataPtr =
580         std::make_unique<TimerData>(std::make_pair(type, std::move(*pkg)));
581     Timer timer(_event,
582                 std::bind(&Manager::timerExpired, this, std::ref(*dataPtr)));
583     if (type == TimerType::repeating)
584     {
585         timer.restart(interval);
586     }
587     else if (type == TimerType::oneshot)
588     {
589         timer.restartOnce(interval);
590     }
591     else
592     {
593         throw std::invalid_argument("Invalid Timer Type");
594     }
595     _timers.emplace_back(std::move(dataPtr), std::move(timer));
596 }
597 
598 void Manager::addGroup(const Group& group)
599 {
600     const auto& members = group.getMembers();
601     for (const auto& member : members)
602     {
603         try
604         {
605             auto service = getService(member, group.getInterface());
606 
607             auto variant =
608                 util::SDBusPlus::getPropertyVariant<PropertyVariantType>(
609                     service, member, group.getInterface(), group.getProperty());
610 
611             setProperty(member, group.getInterface(), group.getProperty(),
612                         variant);
613         }
614         catch (const std::exception& e)
615         {
616             try
617             {
618                 _objects.at(member)
619                     .at(group.getInterface())
620                     .erase(group.getProperty());
621             }
622             catch (const std::out_of_range&)
623             {}
624         }
625     }
626 }
627 
628 void Manager::timerExpired(TimerData& data)
629 {
630     if (std::get<bool>(data.second))
631     {
632         const auto& groups = std::get<const std::vector<Group>&>(data.second);
633         std::for_each(groups.begin(), groups.end(),
634                       [this](const auto& group) { addGroup(group); });
635     }
636 
637     auto& actions =
638         std::get<std::vector<std::unique_ptr<ActionBase>>&>(data.second);
639     // Perform the actions in the timer data
640     std::for_each(actions.begin(), actions.end(),
641                   [](auto& action) { action->run(); });
642 
643     // Remove oneshot timers after they expired
644     if (data.first == TimerType::oneshot)
645     {
646         auto itTimer = std::find_if(
647             _timers.begin(), _timers.end(), [&data](const auto& timer) {
648                 return (data.first == timer.first->first &&
649                         (std::get<std::string>(data.second) ==
650                          std::get<std::string>(timer.first->second)));
651             });
652         if (itTimer != std::end(_timers))
653         {
654             _timers.erase(itTimer);
655         }
656     }
657 }
658 
659 void Manager::handleSignal(sdbusplus::message::message& msg,
660                            const std::vector<SignalPkg>* pkgs)
661 {
662     for (auto& pkg : *pkgs)
663     {
664         // Handle the signal callback and only run the actions if the handler
665         // updated the cache for the given SignalObject
666         if (std::get<SignalHandler>(pkg)(msg, std::get<SignalObject>(pkg),
667                                          *this))
668         {
669             // Perform the actions in the handler package
670             auto& actions = std::get<SignalActions>(pkg);
671             std::for_each(actions.begin(), actions.end(),
672                           [](auto& action) { action.get()->run(); });
673         }
674         // Only rewind message when not last package
675         if (&pkg != &pkgs->back())
676         {
677             sd_bus_message_rewind(msg.get(), true);
678         }
679     }
680 }
681 
682 void Manager::setProfiles()
683 {
684     // Profiles JSON config file is optional
685     auto confFile = fan::JsonConfig::getConfFile(_bus, confAppName,
686                                                  Profile::confFileName, true);
687 
688     _profiles.clear();
689     if (!confFile.empty())
690     {
691         for (const auto& entry : fan::JsonConfig::load(confFile))
692         {
693             auto obj = std::make_unique<Profile>(entry);
694             _profiles.emplace(
695                 std::make_pair(obj->getName(), obj->getProfiles()),
696                 std::move(obj));
697         }
698     }
699 
700     // Ensure all configurations use the same set of active profiles
701     // (In case a profile's active state changes during configuration)
702     _activeProfiles.clear();
703     for (const auto& profile : _profiles)
704     {
705         if (profile.second->isActive())
706         {
707             _activeProfiles.emplace_back(profile.first.first);
708         }
709     }
710 }
711 
712 } // namespace phosphor::fan::control::json
713