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