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