| /** |
| * Copyright © 2020 IBM Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "json_parser.hpp" |
| |
| #include "conditions.hpp" |
| #include "json_config.hpp" |
| #include "nonzero_speed_trust.hpp" |
| #include "power_interface.hpp" |
| #include "power_off_rule.hpp" |
| #include "tach_sensor.hpp" |
| #include "types.hpp" |
| |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <algorithm> |
| #include <format> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <vector> |
| |
| namespace phosphor::fan::monitor |
| { |
| |
| using json = nlohmann::json; |
| using namespace phosphor::logging; |
| |
| namespace tClass |
| { |
| |
| // Get a constructed trust group class for a non-zero speed group |
| CreateGroupFunction |
| getNonZeroSpeed(const std::vector<trust::GroupDefinition>& group) |
| { |
| return [group]() { |
| return std::make_unique<trust::NonzeroSpeed>(std::move(group)); |
| }; |
| } |
| |
| } // namespace tClass |
| |
| const std::map<std::string, trustHandler> trusts = { |
| {"nonzerospeed", tClass::getNonZeroSpeed}}; |
| const std::map<std::string, condHandler> conditions = { |
| {"propertiesmatch", condition::getPropertiesMatch}}; |
| const std::map<std::string, size_t> methods = { |
| {"timebased", MethodMode::timebased}, {"count", MethodMode::count}}; |
| |
| const std::vector<CreateGroupFunction> getTrustGrps(const json& obj) |
| { |
| std::vector<CreateGroupFunction> grpFuncs; |
| |
| if (obj.contains("sensor_trust_groups")) |
| { |
| for (auto& stg : obj["sensor_trust_groups"]) |
| { |
| if (!stg.contains("class") || !stg.contains("group")) |
| { |
| // Log error on missing required parameters |
| log<level::ERR>( |
| "Missing required fan monitor trust group parameters", |
| entry("REQUIRED_PARAMETERS=%s", "{class, group}")); |
| throw std::runtime_error( |
| "Missing required fan trust group parameters"); |
| } |
| auto tgClass = stg["class"].get<std::string>(); |
| std::vector<trust::GroupDefinition> group; |
| for (auto& member : stg["group"]) |
| { |
| // Construct list of group members |
| if (!member.contains("name")) |
| { |
| // Log error on missing required parameter |
| log<level::ERR>( |
| "Missing required fan monitor trust group member name", |
| entry("CLASS=%s", tgClass.c_str())); |
| throw std::runtime_error( |
| "Missing required fan monitor trust group member name"); |
| } |
| auto in_trust = true; |
| if (member.contains("in_trust")) |
| { |
| in_trust = member["in_trust"].get<bool>(); |
| } |
| group.emplace_back(trust::GroupDefinition{ |
| member["name"].get<std::string>(), in_trust}); |
| } |
| // The class for fan sensor trust groups |
| // (Must have a supported function within the tClass namespace) |
| std::transform(tgClass.begin(), tgClass.end(), tgClass.begin(), |
| tolower); |
| auto handler = trusts.find(tgClass); |
| if (handler != trusts.end()) |
| { |
| // Call function for trust group class |
| grpFuncs.emplace_back(handler->second(group)); |
| } |
| else |
| { |
| // Log error on unsupported trust group class |
| log<level::ERR>("Invalid fan monitor trust group class", |
| entry("CLASS=%s", tgClass.c_str())); |
| throw std::runtime_error( |
| "Invalid fan monitor trust group class"); |
| } |
| } |
| } |
| |
| return grpFuncs; |
| } |
| |
| const std::vector<SensorDefinition> getSensorDefs(const json& sensors) |
| { |
| std::vector<SensorDefinition> sensorDefs; |
| |
| for (const auto& sensor : sensors) |
| { |
| if (!sensor.contains("name") || !sensor.contains("has_target")) |
| { |
| // Log error on missing required parameters |
| log<level::ERR>( |
| "Missing required fan sensor definition parameters", |
| entry("REQUIRED_PARAMETERS=%s", "{name, has_target}")); |
| throw std::runtime_error( |
| "Missing required fan sensor definition parameters"); |
| } |
| // Target interface is optional and defaults to |
| // 'xyz.openbmc_project.Control.FanSpeed' |
| std::string targetIntf = "xyz.openbmc_project.Control.FanSpeed"; |
| if (sensor.contains("target_interface")) |
| { |
| targetIntf = sensor["target_interface"].get<std::string>(); |
| } |
| // Target path is optional |
| std::string targetPath; |
| if (sensor.contains("target_path")) |
| { |
| targetPath = sensor["target_path"].get<std::string>(); |
| } |
| // Factor is optional and defaults to 1 |
| double factor = 1.0; |
| if (sensor.contains("factor")) |
| { |
| factor = sensor["factor"].get<double>(); |
| } |
| // Offset is optional and defaults to 0 |
| int64_t offset = 0; |
| if (sensor.contains("offset")) |
| { |
| offset = sensor["offset"].get<int64_t>(); |
| } |
| // Threshold is optional and defaults to 1 |
| size_t threshold = 1; |
| if (sensor.contains("threshold")) |
| { |
| threshold = sensor["threshold"].get<size_t>(); |
| } |
| // Ignore being above the allowed max is optional, defaults to not |
| bool ignoreAboveMax = false; |
| if (sensor.contains("ignore_above_max")) |
| { |
| ignoreAboveMax = sensor["ignore_above_max"].get<bool>(); |
| } |
| |
| SensorDefinition def{ |
| .name = sensor["name"].get<std::string>(), |
| .hasTarget = sensor["has_target"].get<bool>(), |
| .targetInterface = targetIntf, |
| .targetPath = targetPath, |
| .factor = factor, |
| .offset = offset, |
| .threshold = threshold, |
| .ignoreAboveMax = ignoreAboveMax}; |
| |
| sensorDefs.push_back(std::move(def)); |
| } |
| |
| return sensorDefs; |
| } |
| |
| const std::vector<FanDefinition> getFanDefs(const json& obj) |
| { |
| std::vector<FanDefinition> fanDefs; |
| |
| for (const auto& fan : obj["fans"]) |
| { |
| if (!fan.contains("inventory") || !fan.contains("deviation") || |
| !fan.contains("sensors")) |
| { |
| // Log error on missing required parameters |
| log<level::ERR>( |
| "Missing required fan monitor definition parameters", |
| entry("REQUIRED_PARAMETERS=%s", |
| "{inventory, deviation, sensors}")); |
| throw std::runtime_error( |
| "Missing required fan monitor definition parameters"); |
| } |
| // Valid deviation range is 0 - 100% |
| auto deviation = fan["deviation"].get<size_t>(); |
| if (100 < deviation) |
| { |
| auto msg = std::format( |
| "Invalid deviation of {} found, must be between 0 and 100", |
| deviation); |
| |
| log<level::ERR>(msg.c_str()); |
| throw std::runtime_error(msg.c_str()); |
| } |
| |
| // Upper deviation defaults to the deviation value and |
| // can also be separately specified. |
| size_t upperDeviation = deviation; |
| if (fan.contains("upper_deviation")) |
| { |
| upperDeviation = fan["upper_deviation"].get<size_t>(); |
| if (100 < upperDeviation) |
| { |
| auto msg = |
| std::format("Invalid upper_deviation of {} found, must " |
| "be between 0 and 100", |
| upperDeviation); |
| |
| log<level::ERR>(msg.c_str()); |
| throw std::runtime_error(msg.c_str()); |
| } |
| } |
| |
| // Construct the sensor definitions for this fan |
| auto sensorDefs = getSensorDefs(fan["sensors"]); |
| |
| // Functional delay is optional and defaults to 0 |
| size_t funcDelay = 0; |
| if (fan.contains("functional_delay")) |
| { |
| funcDelay = fan["functional_delay"].get<size_t>(); |
| } |
| |
| // Method is optional and defaults to time based functional |
| // determination |
| size_t method = MethodMode::timebased; |
| size_t countInterval = 1; |
| if (fan.contains("method")) |
| { |
| auto methodConf = fan["method"].get<std::string>(); |
| auto methodFunc = methods.find(methodConf); |
| if (methodFunc != methods.end()) |
| { |
| method = methodFunc->second; |
| } |
| else |
| { |
| // Log error on unsupported method parameter |
| log<level::ERR>("Invalid fan method"); |
| throw std::runtime_error("Invalid fan method"); |
| } |
| |
| // Read the count interval value used with the count method. |
| if (method == MethodMode::count) |
| { |
| if (fan.contains("count_interval")) |
| { |
| countInterval = fan["count_interval"].get<size_t>(); |
| } |
| } |
| } |
| |
| // Timeout defaults to 0 |
| size_t timeout = 0; |
| if (method == MethodMode::timebased) |
| { |
| if (!fan.contains("allowed_out_of_range_time")) |
| { |
| // Log error on missing required parameter |
| log<level::ERR>( |
| "Missing required fan monitor definition parameters", |
| entry("REQUIRED_PARAMETER=%s", |
| "{allowed_out_of_range_time}")); |
| throw std::runtime_error( |
| "Missing required fan monitor definition parameters"); |
| } |
| else |
| { |
| timeout = fan["allowed_out_of_range_time"].get<size_t>(); |
| } |
| } |
| |
| // Monitor start delay is optional and defaults to 0 |
| size_t monitorDelay = 0; |
| if (fan.contains("monitor_start_delay")) |
| { |
| monitorDelay = fan["monitor_start_delay"].get<size_t>(); |
| } |
| |
| // num_sensors_nonfunc_for_fan_nonfunc is optional and defaults |
| // to zero if not present, meaning the code will not set the |
| // parent fan to nonfunctional based on sensors. |
| size_t nonfuncSensorsCount = 0; |
| if (fan.contains("num_sensors_nonfunc_for_fan_nonfunc")) |
| { |
| nonfuncSensorsCount = |
| fan["num_sensors_nonfunc_for_fan_nonfunc"].get<size_t>(); |
| } |
| |
| // nonfunc_rotor_error_delay is optional, though it will |
| // default to zero if 'fault_handling' is present. |
| std::optional<size_t> nonfuncRotorErrorDelay; |
| if (fan.contains("nonfunc_rotor_error_delay")) |
| { |
| nonfuncRotorErrorDelay = |
| fan["nonfunc_rotor_error_delay"].get<size_t>(); |
| } |
| else if (obj.contains("fault_handling")) |
| { |
| nonfuncRotorErrorDelay = 0; |
| } |
| |
| // fan_missing_error_delay is optional. |
| std::optional<size_t> fanMissingErrorDelay; |
| if (fan.contains("fan_missing_error_delay")) |
| { |
| fanMissingErrorDelay = |
| fan.at("fan_missing_error_delay").get<size_t>(); |
| } |
| |
| // Handle optional conditions |
| auto cond = std::optional<Condition>(); |
| if (fan.contains("condition")) |
| { |
| if (!fan["condition"].contains("name")) |
| { |
| // Log error on missing required parameter |
| log<level::ERR>( |
| "Missing required fan monitor condition parameter", |
| entry("REQUIRED_PARAMETER=%s", "{name}")); |
| throw std::runtime_error( |
| "Missing required fan monitor condition parameter"); |
| } |
| auto name = fan["condition"]["name"].get<std::string>(); |
| // The function for fan monitoring condition |
| // (Must have a supported function within the condition namespace) |
| std::transform(name.begin(), name.end(), name.begin(), tolower); |
| auto handler = conditions.find(name); |
| if (handler != conditions.end()) |
| { |
| cond = handler->second(fan["condition"]); |
| } |
| else |
| { |
| log<level::INFO>( |
| "No handler found for configured condition", |
| entry("CONDITION_NAME=%s", name.c_str()), |
| entry("JSON_DUMP=%s", fan["condition"].dump().c_str())); |
| } |
| } |
| |
| // if the fan should be set to functional when plugged in |
| bool setFuncOnPresent = false; |
| if (fan.contains("set_func_on_present")) |
| { |
| setFuncOnPresent = fan["set_func_on_present"].get<bool>(); |
| } |
| |
| FanDefinition def{ |
| .name = fan["inventory"].get<std::string>(), |
| .method = method, |
| .funcDelay = funcDelay, |
| .timeout = timeout, |
| .deviation = deviation, |
| .upperDeviation = upperDeviation, |
| .numSensorFailsForNonfunc = nonfuncSensorsCount, |
| .monitorStartDelay = monitorDelay, |
| .countInterval = countInterval, |
| .nonfuncRotorErrDelay = nonfuncRotorErrorDelay, |
| .fanMissingErrDelay = fanMissingErrorDelay, |
| .sensorList = std::move(sensorDefs), |
| .condition = cond, |
| .funcOnPresent = setFuncOnPresent}; |
| |
| fanDefs.push_back(std::move(def)); |
| } |
| |
| return fanDefs; |
| } |
| |
| PowerRuleState getPowerOffPowerRuleState(const json& powerOffConfig) |
| { |
| // The state is optional and defaults to runtime |
| PowerRuleState ruleState{PowerRuleState::runtime}; |
| |
| if (powerOffConfig.contains("state")) |
| { |
| auto state = powerOffConfig.at("state").get<std::string>(); |
| if (state == "at_pgood") |
| { |
| ruleState = PowerRuleState::atPgood; |
| } |
| else if (state != "runtime") |
| { |
| auto msg = std::format("Invalid power off state entry {}", state); |
| log<level::ERR>(msg.c_str()); |
| throw std::runtime_error(msg.c_str()); |
| } |
| } |
| |
| return ruleState; |
| } |
| |
| std::unique_ptr<PowerOffCause> getPowerOffCause(const json& powerOffConfig) |
| { |
| std::unique_ptr<PowerOffCause> cause; |
| |
| if (!powerOffConfig.contains("count") || !powerOffConfig.contains("cause")) |
| { |
| const auto msg = |
| "Missing 'count' or 'cause' entries in power off config"; |
| log<level::ERR>(msg); |
| throw std::runtime_error(msg); |
| } |
| |
| auto count = powerOffConfig.at("count").get<size_t>(); |
| auto powerOffCause = powerOffConfig.at("cause").get<std::string>(); |
| |
| const std::map<std::string, std::function<std::unique_ptr<PowerOffCause>()>> |
| causes{ |
| {"missing_fan_frus", |
| [count]() { return std::make_unique<MissingFanFRUCause>(count); }}, |
| {"nonfunc_fan_rotors", |
| [count]() { |
| return std::make_unique<NonfuncFanRotorCause>(count); |
| }}, |
| {"fan_frus_with_nonfunc_rotors", [count]() { |
| return std::make_unique<FanFRUsWithNonfuncRotorsCause>(count); |
| }}}; |
| |
| auto it = causes.find(powerOffCause); |
| if (it != causes.end()) |
| { |
| cause = it->second(); |
| } |
| else |
| { |
| auto msg = |
| std::format("Invalid power off cause {} in power off config JSON", |
| powerOffCause); |
| log<level::ERR>(msg.c_str()); |
| throw std::runtime_error(msg.c_str()); |
| } |
| |
| return cause; |
| } |
| |
| std::unique_ptr<PowerOffAction> |
| getPowerOffAction(const json& powerOffConfig, |
| std::shared_ptr<PowerInterfaceBase>& powerInterface, |
| PowerOffAction::PrePowerOffFunc& func) |
| { |
| std::unique_ptr<PowerOffAction> action; |
| if (!powerOffConfig.contains("type")) |
| { |
| const auto msg = "Missing 'type' entry in power off config"; |
| log<level::ERR>(msg); |
| throw std::runtime_error(msg); |
| } |
| |
| auto type = powerOffConfig.at("type").get<std::string>(); |
| |
| if (((type == "hard") || (type == "soft")) && |
| !powerOffConfig.contains("delay")) |
| { |
| const auto msg = "Missing 'delay' entry in power off config"; |
| log<level::ERR>(msg); |
| throw std::runtime_error(msg); |
| } |
| else if ((type == "epow") && |
| (!powerOffConfig.contains("service_mode_delay") || |
| !powerOffConfig.contains("meltdown_delay"))) |
| { |
| const auto msg = "Missing 'service_mode_delay' or 'meltdown_delay' " |
| "entry in power off config"; |
| log<level::ERR>(msg); |
| throw std::runtime_error(msg); |
| } |
| |
| if (type == "hard") |
| { |
| action = std::make_unique<HardPowerOff>( |
| powerOffConfig.at("delay").get<uint32_t>(), powerInterface, func); |
| } |
| else if (type == "soft") |
| { |
| action = std::make_unique<SoftPowerOff>( |
| powerOffConfig.at("delay").get<uint32_t>(), powerInterface, func); |
| } |
| else if (type == "epow") |
| { |
| action = std::make_unique<EpowPowerOff>( |
| powerOffConfig.at("service_mode_delay").get<uint32_t>(), |
| powerOffConfig.at("meltdown_delay").get<uint32_t>(), powerInterface, |
| func); |
| } |
| else |
| { |
| auto msg = |
| std::format("Invalid 'type' entry {} in power off config", type); |
| log<level::ERR>(msg.c_str()); |
| throw std::runtime_error(msg.c_str()); |
| } |
| |
| return action; |
| } |
| |
| std::vector<std::unique_ptr<PowerOffRule>> getPowerOffRules( |
| const json& obj, std::shared_ptr<PowerInterfaceBase>& powerInterface, |
| PowerOffAction::PrePowerOffFunc& func) |
| { |
| std::vector<std::unique_ptr<PowerOffRule>> rules; |
| |
| if (!(obj.contains("fault_handling") && |
| obj.at("fault_handling").contains("power_off_config"))) |
| { |
| return rules; |
| } |
| |
| for (const auto& config : obj.at("fault_handling").at("power_off_config")) |
| { |
| auto state = getPowerOffPowerRuleState(config); |
| auto cause = getPowerOffCause(config); |
| auto action = getPowerOffAction(config, powerInterface, func); |
| |
| auto rule = std::make_unique<PowerOffRule>( |
| std::move(state), std::move(cause), std::move(action)); |
| rules.push_back(std::move(rule)); |
| } |
| |
| return rules; |
| } |
| |
| std::optional<size_t> getNumNonfuncRotorsBeforeError(const json& obj) |
| { |
| std::optional<size_t> num; |
| |
| if (obj.contains("fault_handling")) |
| { |
| // Defaults to 1 if not present inside of 'fault_handling'. |
| num = obj.at("fault_handling") |
| .value("num_nonfunc_rotors_before_error", 1); |
| } |
| |
| return num; |
| } |
| |
| } // namespace phosphor::fan::monitor |