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