| #include "tlbmc/configs/entity_config_json_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> // NOLINT: filesystem is commonly used in bmc codebase. |
| #include <fstream> |
| #include <memory> |
| #include <optional> |
| #include <queue> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_replace.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/strip.h" |
| #include "absl/strings/substitute.h" |
| #include "absl/time/time.h" |
| #include "absl/types/span.h" |
| #include "g3/macros.h" |
| #include "time/proto.h" |
| #include "nlohmann/json_fwd.hpp" |
| #include "nlohmann/json.hpp" |
| #include "json_utils.h" |
| #include "tlbmc/central_config/config.h" |
| #include "tlbmc/collector/sensor_collector.h" |
| #include "entity_common_config.pb.h" |
| #include "tlbmc/configs/entity_config.h" |
| #include "tlbmc/configs/expression.h" |
| #include "fan_controller_config.pb.h" |
| #include "fan_pwm_config.pb.h" |
| #include "fan_tach_config.pb.h" |
| #include "hal_common_config.pb.h" |
| #include "hwmon_temp_sensor_config.pb.h" |
| #include "intel_cpu_sensor_config.pb.h" |
| #include "nic_telemetry_config.pb.h" |
| #include "psu_sensor_config.pb.h" |
| #include "reading_range_config.pb.h" |
| #include "reading_transform_config.pb.h" |
| #include "shared_mem_sensor_config.pb.h" |
| #include "threshold_config.pb.h" |
| #include "topology_config.pb.h" |
| #include "virtual_sensor_config.pb.h" |
| #include "veeprom.pb.h" |
| #include "tlbmc/rcu/simple_rcu.h" |
| #include "tlbmc/redfish/data/stable_id.h" |
| #include "fru.pb.h" |
| #include "resource.pb.h" |
| #include "sensor.pb.h" |
| #include "router_interface.h" |
| #include "re2/re2.h" |
| |
| namespace milotic_tlbmc { |
| |
| namespace { |
| |
| using ::crow::RouterInterface; |
| using ::milotic::authz::GetValueAsArray; |
| using ::milotic::authz::GetValueAsBool; |
| using ::milotic::authz::GetValueAsDoubleFromFloatOrInteger; |
| using ::milotic::authz::GetValueAsJson; |
| using ::milotic::authz::GetValueAsString; |
| using ::milotic::authz::GetValueAsUint; |
| using ::milotic::authz::GetValueAsUintFromStringOrInteger; |
| |
| // ProbedConfigMap is a map of config name to ProbedConfigData, which is a |
| // struct containing corresponding config json and matched probed fru_keys. |
| using ProbedConfigMap = absl::flat_hash_map<std::string, ProbedConfigData>; |
| |
| // We need to store what has already been probed and ad-hoc FRUs that could be |
| // probed. |
| struct ProcessedConfigData { |
| ProbedConfigMap probed_config_map; |
| absl::flat_hash_map<std::string, nlohmann::json> unscanned_ad_hoc_fru_configs; |
| }; |
| |
| constexpr absl::string_view kRefreshIntervalKeyword = "RefreshIntervalMs"; |
| constexpr absl::string_view kQueueSizeKeyword = "QueueSize"; |
| |
| constexpr absl::string_view kDownstreamPortKeyword = "PortDownstream"; |
| constexpr absl::string_view kUpstreamConnectionKeyword = "UpstreamConnection"; |
| // We don't set devpath for some configs, e.g. Ncsi Cable, so we can use this |
| // keyword to skip setting devpath for them. |
| constexpr absl::string_view kDontSetDevpathKeyword = "DontSetDevpath"; |
| constexpr const char* kProbeKeyword = "ProbeV2"; |
| constexpr const char* kRedfishAggregationKeyword = "REDFISH_AGGREGATION"; |
| |
| constexpr absl::string_view kBmcNetKeyword = "BmcNet"; |
| constexpr absl::string_view kChassisTypeKeyword = "ChassisType"; |
| constexpr absl::string_view kStorageKeyword = "Storage"; |
| constexpr absl::string_view kProcessorKeyword = "Processor"; |
| constexpr absl::string_view kDimmKeyword = "DIMM"; |
| constexpr absl::string_view kFanKeyword = "Fan"; |
| constexpr absl::string_view kRelatedItemKeyword = "RelatedItem"; |
| constexpr absl::string_view kResourceTypeKeyword = "ResourceType"; |
| constexpr absl::string_view kPartLocationTypeKeyword = "PartLocationType"; |
| constexpr absl::string_view kRootChassisLocationCodeKeyword = |
| "RootChassisLocationCode"; |
| constexpr absl::string_view kSensorGroupKeyword = "SensorGroup"; |
| constexpr absl::string_view kCpuIdKeyword = "CpuID"; |
| constexpr absl::string_view kDtsOffsetKeyword = "DtsCritOffset"; |
| |
| constexpr std::array<std::pair<std::string_view, SensorUnit>, 7> |
| kSupportedSensorUnits = {{ |
| // go/keep-sorted start |
| {"Ampere", SensorUnit::UNIT_AMPERE}, |
| {"Count", SensorUnit::UNIT_COUNT}, |
| {"DegreeCelsius", SensorUnit::UNIT_DEGREE_CELSIUS}, |
| {"Percent", SensorUnit::UNIT_PERCENT}, |
| {"RPM", SensorUnit::UNIT_REVOLUTION_PER_MINUTE}, |
| {"Volt", SensorUnit::UNIT_VOLT}, |
| {"Watt", SensorUnit::UNIT_WATT}, |
| // go/keep-sorted end |
| }}; |
| |
| absl::Status ParseStorageConfig(const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node, |
| size_t index) { |
| const nlohmann::json* storage_config = |
| GetValueAsJson(config, kStorageKeyword); |
| if (storage_config == nullptr) { |
| return absl::OkStatus(); |
| } |
| const std::string* storage_id = GetValueAsString(*storage_config, "Id"); |
| if (storage_id == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Id field is not found for storage config"); |
| } |
| std::string storage_id_with_index = *storage_id; |
| absl::StrReplaceAll({{"$index", absl::StrCat(index + 1)}}, |
| &storage_id_with_index); |
| topology_config_node.add_children_storage_ids(storage_id_with_index); |
| |
| return absl::OkStatus(); |
| } |
| |
| void ParseBmcNetConfig(const nlohmann::json& config, Fru& fru) { |
| const std::string* type_str = GetValueAsString(config, "Type"); |
| if (type_str == nullptr || *type_str != kBmcNetKeyword) { |
| return; |
| } |
| fru.mutable_attributes()->mutable_chassis_properties()->set_bmcnet(true); |
| } |
| |
| absl::Status ParsePortConfig(const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node) { |
| const std::string* port_type_str = GetValueAsString(config, "Type"); |
| if (port_type_str == nullptr || |
| (*port_type_str != kUpstreamConnectionKeyword && |
| *port_type_str != kDownstreamPortKeyword)) { |
| return absl::OkStatus(); |
| } |
| |
| const std::string* port_name = GetValueAsString(config, "Name"); |
| if (port_name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not found for port"); |
| } |
| |
| PortConfig port_config; |
| port_config.set_port_name(*port_name); |
| if (*port_type_str == kUpstreamConnectionKeyword) { |
| if (std::any_of(topology_config_node.upstream_port_configs().cbegin(), |
| topology_config_node.upstream_port_configs().cend(), |
| [&port_name](const PortConfig& upstream_port_config) { |
| return upstream_port_config.port_name() == *port_name; |
| })) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: Multiple Upstream connection configs present for ", |
| *port_name)); |
| } |
| port_config.set_port_type(PORT_TYPE_UPSTREAM); |
| *topology_config_node.add_upstream_port_configs() = std::move(port_config); |
| } else if (*port_type_str == kDownstreamPortKeyword) { |
| const std::string* port_label = GetValueAsString(config, "Label"); |
| if (port_label == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Label field is not found for downstream port"); |
| } |
| port_config.set_port_label(*port_label); |
| port_config.set_port_type(PORT_TYPE_DOWNSTREAM); |
| topology_config_node.mutable_port_configs()->insert( |
| {*port_name, std::move(port_config)}); |
| } |
| return absl::OkStatus(); |
| } |
| |
| ChassisType ParseChassisType(const nlohmann::json& config) { |
| const std::string* chassis_type_str = |
| GetValueAsString(config, kChassisTypeKeyword); |
| ChassisType chassis_type; |
| if (chassis_type_str == nullptr || |
| !ChassisType_Parse(*chassis_type_str, &chassis_type)) { |
| LOG(INFO) << "No valid chassis type found for: " << config.dump() |
| << " setting to default RackMount"; |
| chassis_type = CHASSIS_TYPE_RACK_MOUNT; |
| } |
| |
| return chassis_type; |
| } |
| |
| absl::StatusOr<ResourceType> ParseResourceType(const nlohmann::json& config) { |
| const std::string* resource_type_str = |
| GetValueAsString(config, kResourceTypeKeyword); |
| if (resource_type_str == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: No resource type found for config ", config.dump())); |
| } |
| |
| ResourceType resource_type; |
| if (!ResourceType_Parse(*resource_type_str, &resource_type)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: Invalid resource type found for config: ", |
| *resource_type_str)); |
| } |
| |
| return resource_type; |
| } |
| |
| void ParsePartLocationType(const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node) { |
| const std::string* location_type_str = |
| GetValueAsString(config, kPartLocationTypeKeyword); |
| if (location_type_str == nullptr) { |
| LOG(INFO) << "No location type found for config: " << config.dump(); |
| return; |
| } |
| |
| PartLocationType location_type; |
| if (!PartLocationType_Parse(*location_type_str, &location_type)) { |
| LOG(WARNING) << "Invalid resource type found for config: " |
| << *location_type_str; |
| return; |
| } |
| |
| topology_config_node.mutable_location_context()->set_location_type( |
| location_type); |
| } |
| |
| void ParseRootChassisLocationCode(const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node) { |
| const std::string* root_chassis_location_code = |
| GetValueAsString(config, kRootChassisLocationCodeKeyword); |
| if (root_chassis_location_code != nullptr) { |
| DLOG(INFO) << "Root chassis location code found: " |
| << *root_chassis_location_code; |
| topology_config_node.set_root_chassis_location_code( |
| *root_chassis_location_code); |
| } |
| } |
| |
| std::optional<RelatedItem> ParseRelatedItem(const nlohmann::json& config) { |
| const nlohmann::json* related_item_json = |
| GetValueAsJson(config, kRelatedItemKeyword); |
| if (related_item_json == nullptr) { |
| LOG(INFO) << "No related item found for config: " << config.dump(); |
| return std::nullopt; |
| } |
| const std::string* id = GetValueAsString(*related_item_json, "Id"); |
| if (id == nullptr) { |
| LOG(INFO) << "No item name found for related item config: " |
| << config.dump(); |
| return std::nullopt; |
| } |
| ResourceType type; |
| const std::string* type_str = GetValueAsString(*related_item_json, "Type"); |
| if (type_str == nullptr || !ResourceType_Parse(*type_str, &type)) { |
| type = RESOURCE_TYPE_BOARD; |
| } |
| |
| RelatedItem related_item; |
| related_item.set_id(*id); |
| related_item.set_type(type); |
| return related_item; |
| } |
| |
| absl::Status ParseAssetConfig(const nlohmann::json& config, Fru& fru_object) { |
| const nlohmann::json* asset_config_ptr = GetValueAsJson(config, "Asset"); |
| if (asset_config_ptr == nullptr) { |
| return absl::OkStatus(); |
| } |
| |
| AssetInfo asset; |
| nlohmann::json asset_config = *asset_config_ptr; |
| |
| const std::string* manufacturer_ptr = |
| GetValueAsString(asset_config, "Manufacturer"); |
| if (manufacturer_ptr != nullptr) { |
| if (absl::AsciiStrToUpper(*manufacturer_ptr) == "$BOARD_MANUFACTURER" && |
| fru_object.data().fru_info().has_board_manufacturer()) { |
| asset.set_manufacturer(fru_object.data().fru_info().board_manufacturer()); |
| } else if (absl::AsciiStrToUpper(*manufacturer_ptr) == |
| "$PRODUCT_MANUFACTURER" && |
| fru_object.data().fru_info().has_product_manufacturer()) { |
| asset.set_manufacturer( |
| fru_object.data().fru_info().product_manufacturer()); |
| } else if (absl::StartsWith(*manufacturer_ptr, "$")) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid Asset config, cannot substitute ", *manufacturer_ptr, |
| ". Check this is a valid substitution keyword and scanned Fru " |
| "contains this field. Config: ", |
| config.dump())); |
| } else { |
| asset.set_manufacturer(*manufacturer_ptr); |
| } |
| } |
| |
| const std::string* model_ptr = GetValueAsString(asset_config, "Model"); |
| if (model_ptr != nullptr) { |
| if (absl::AsciiStrToUpper(*model_ptr) == "$BOARD_PRODUCT_NAME" && |
| fru_object.data().fru_info().has_board_product_name()) { |
| asset.set_product_name(fru_object.data().fru_info().board_product_name()); |
| } else if (absl::AsciiStrToUpper(*model_ptr) == "$PRODUCT_PRODUCT_NAME" && |
| fru_object.data().fru_info().has_product_product_name()) { |
| asset.set_product_name( |
| fru_object.data().fru_info().product_product_name()); |
| } else if (absl::StartsWith(*model_ptr, "$")) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid Asset config, cannot substitute ", *model_ptr, |
| ". Check this is a valid substitution keyword and scanned Fru " |
| "contains this field.")); |
| } else { |
| asset.set_product_name(*model_ptr); |
| } |
| } |
| |
| const std::string* part_number_ptr = |
| GetValueAsString(asset_config, "PartNumber"); |
| if (part_number_ptr != nullptr) { |
| if (absl::AsciiStrToUpper(*part_number_ptr) == "$BOARD_PART_NUMBER" && |
| fru_object.data().fru_info().has_board_part_number()) { |
| asset.set_part_number(fru_object.data().fru_info().board_part_number()); |
| } else if (absl::AsciiStrToUpper(*part_number_ptr) == |
| "$PRODUCT_PART_NUMBER" && |
| fru_object.data().fru_info().has_product_part_number()) { |
| asset.set_part_number(fru_object.data().fru_info().product_part_number()); |
| } else if (absl::StartsWith(*part_number_ptr, "$")) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid Asset config, cannot substitute ", *part_number_ptr, |
| ". Check this is a valid substitution keyword and scanned Fru " |
| "contains this field.")); |
| } else { |
| asset.set_part_number(*part_number_ptr); |
| } |
| } |
| |
| const std::string* serial_number_ptr = |
| GetValueAsString(asset_config, "SerialNumber"); |
| if (serial_number_ptr != nullptr) { |
| if (absl::AsciiStrToUpper(*serial_number_ptr) == "$BOARD_SERIAL_NUMBER" && |
| fru_object.data().fru_info().has_board_serial_number()) { |
| asset.set_serial_number( |
| fru_object.data().fru_info().board_serial_number()); |
| } else if (absl::AsciiStrToUpper(*serial_number_ptr) == |
| "$PRODUCT_SERIAL_NUMBER" && |
| fru_object.data().fru_info().has_product_serial_number()) { |
| asset.set_serial_number( |
| fru_object.data().fru_info().product_serial_number()); |
| } else if (absl::StartsWith(*serial_number_ptr, "$")) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid Asset config, cannot substitute ", *serial_number_ptr, |
| ". Check this is a valid substitution keyword and scanned Fru " |
| "contains this field.")); |
| } else { |
| asset.set_serial_number(*serial_number_ptr); |
| } |
| } |
| |
| *fru_object.mutable_data()->mutable_asset_info() = asset; |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<nlohmann::json> GetObject(const nlohmann::json& json_obj, |
| const std::string& key) { |
| auto find_key = json_obj.find(key); |
| if (find_key == json_obj.end()) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: ", key, " field is not found: ", json_obj.dump())); |
| } |
| return find_key->get<nlohmann::json>(); |
| } |
| |
| absl::StatusOr<std::string> ParseConfigName(const nlohmann::json& config) { |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: Name field is not found: ", config.dump())); |
| } |
| if (name->empty()) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: Name field is empty: ", config.dump())); |
| } |
| return *name; |
| } |
| |
| absl::StatusOr<uint64_t> ParseCpuId(const nlohmann::json& config) { |
| std::optional<uint64_t> cpu_id = |
| GetValueAsUintFromStringOrInteger(config, kCpuIdKeyword); |
| if (!cpu_id.has_value()) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: CpuID field is not found: ", config.dump())); |
| } |
| return *cpu_id; |
| } |
| |
| bool CheckProbeFieldMatch(const std::string* probe_field, const Fru& fru, |
| absl::string_view fru_field_name) { |
| auto find_fru_field = fru.data().fields().find(fru_field_name); |
| return probe_field == nullptr || |
| (find_fru_field != fru.data().fields().end() && |
| probe_field != nullptr && |
| RE2::FullMatch(find_fru_field->second, *probe_field)); |
| } |
| |
| bool CheckI2cInfoMatchOrNull(const uint64_t* bus, const uint64_t* address, |
| const Fru& fru) { |
| bool bus_matches_or_null = bus == nullptr || *bus == fru.i2c_info().bus(); |
| bool address_matches_or_null = |
| address == nullptr || *address == fru.i2c_info().address(); |
| return bus_matches_or_null && address_matches_or_null; |
| } |
| |
| // Probes if the ipmi fru matches and updates the fru table and probed |
| // config map. Return true if probed successfully. |
| absl::StatusOr<bool> ProbeFru(nlohmann::json& ipmi_fru_object, |
| const nlohmann::json& config, |
| absl::string_view name, FruTable& fru_table, |
| ProbedConfigMap& probed_config_map) { |
| if (!ipmi_fru_object.is_array()) { |
| LOG(INFO) << "IpmiFru field is not an array, converting to array"; |
| ipmi_fru_object = nlohmann::json::array({ipmi_fru_object}); |
| } |
| bool is_fru_probed = false; |
| for (const auto& ipmi_fru : ipmi_fru_object) { |
| // Get probe fields. |
| const auto* board_product_name = |
| GetValueAsString(ipmi_fru, "BOARD_PRODUCT_NAME"); |
| const auto* product_product_name = |
| GetValueAsString(ipmi_fru, "PRODUCT_PRODUCT_NAME"); |
| const auto* bus = GetValueAsUint(ipmi_fru, "BUS"); |
| const auto* address = GetValueAsUint(ipmi_fru, "ADDRESS"); |
| const auto* part_number = GetValueAsString(ipmi_fru, "BOARD_PART_NUMBER"); |
| const auto* board_info_am2 = GetValueAsString(ipmi_fru, "BOARD_INFO_AM2"); |
| const auto* board_info_am3 = GetValueAsString(ipmi_fru, "BOARD_INFO_AM3"); |
| const auto* board_info_am4 = GetValueAsString(ipmi_fru, "BOARD_INFO_AM4"); |
| const auto* product_info_am2 = |
| GetValueAsString(ipmi_fru, "PRODUCT_INFO_AM2"); |
| if (board_product_name == nullptr && product_product_name == nullptr && |
| bus == nullptr && address == nullptr && part_number == nullptr && |
| board_info_am2 == nullptr && board_info_am3 == nullptr && |
| board_info_am4 == nullptr && product_info_am2 == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: IpmiFru field is not a valid probe"); |
| } |
| |
| // Check if the FRU matches the probe. |
| for (const auto& [key, fru] : fru_table.key_to_fru()) { |
| bool i2c_info_matches_or_null = |
| CheckI2cInfoMatchOrNull(bus, address, fru); |
| |
| // Check if any of the FRU fields match the probe. |
| bool board_product_name_matches_or_null = |
| CheckProbeFieldMatch(board_product_name, fru, "BOARD_PRODUCT_NAME"); |
| bool product_product_name_matches_or_null = CheckProbeFieldMatch( |
| product_product_name, fru, "PRODUCT_PRODUCT_NAME"); |
| bool part_number_matches_or_null = |
| CheckProbeFieldMatch(part_number, fru, "BOARD_PART_NUMBER"); |
| bool board_info_am2_matches_or_null = |
| CheckProbeFieldMatch(board_info_am2, fru, "BOARD_INFO_AM2"); |
| bool board_info_am3_matches_or_null = |
| CheckProbeFieldMatch(board_info_am3, fru, "BOARD_INFO_AM3"); |
| bool board_info_am4_matches_or_null = |
| CheckProbeFieldMatch(board_info_am4, fru, "BOARD_INFO_AM4"); |
| bool product_info_am2_matches_or_null = |
| CheckProbeFieldMatch(product_info_am2, fru, "PRODUCT_INFO_AM2"); |
| |
| // If the FRU doesn't match the probe, continue to the next FRU. |
| if (!i2c_info_matches_or_null || !board_product_name_matches_or_null || |
| !product_product_name_matches_or_null || |
| !part_number_matches_or_null || !board_info_am2_matches_or_null || |
| !board_info_am3_matches_or_null || !board_info_am4_matches_or_null || |
| !product_info_am2_matches_or_null) { |
| continue; |
| } |
| |
| // This condition will check that |
| // 1. Only configs containing in $index can have multiple Fru probe true |
| // 2. A Fru cannot match multiple probes within one config |
| // 3. Multiple configs cannot probe true with the same name |
| if (probed_config_map.contains(name) && |
| !absl::StrContains(name, "$index")) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: Frus match the same config multiple" |
| " times but config name does not contain $index : ", |
| name)); |
| } |
| // If we reach here, the FRU matches the probe and is unique. |
| is_fru_probed = true; |
| auto& probed_data = probed_config_map[name]; |
| |
| // If the key is in the format of "bus:address", we will parse the bus |
| // and address and use them to construct the FruKey object. Otherwise, |
| // default to the string representation of the FruKey. |
| probed_data.fru_keys.push_back(FruKey(key)); |
| probed_data.config = config; |
| LOG(INFO) << "Matched config: " << name << " with FRU: " << key; |
| } |
| } |
| |
| if (is_fru_probed) { |
| auto& probed_data = probed_config_map[name]; |
| std::sort(probed_data.fru_keys.begin(), probed_data.fru_keys.end()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Processes probe configs and returns a map of them. |
| absl::StatusOr<ProcessedConfigData> ProcessConfigs( |
| const std::vector<nlohmann::json>& config_list, FruTable& fru_table) { |
| ProcessedConfigData processed_config_data; |
| ProbedConfigMap probed_config_map; |
| absl::flat_hash_map<std::string, nlohmann::json> ad_hoc_fru_config_map; |
| // Map to store the temporary config map for configs that have FOUND probe. |
| absl::flat_hash_map<std::string, std::vector<ProbedConfigData>> |
| pending_config_name_to_probed_configs; |
| for (const auto& config : config_list) { |
| absl::StatusOr<nlohmann::json> probe_result = |
| GetObject(config, kProbeKeyword); |
| if (!probe_result.ok()) { |
| continue; |
| } |
| |
| // Find name of the config. |
| ECCLESIA_ASSIGN_OR_RETURN(std::string name, ParseConfigName(config)); |
| name = absl::StrReplaceAll(name, {{" ", "_"}}); |
| |
| const std::string* probe_str = probe_result->get_ptr<const std::string*>(); |
| if (probe_str != nullptr) { |
| std::string_view probe_str_view(*probe_str); |
| // If the probe is TRUE, then add a ready FRU. If the probe |
| // is Redfish Aggregation, then add an status_unknown FRU. |
| if (probe_str_view == "TRUE" || |
| probe_str_view == kRedfishAggregationKeyword) { |
| if (probed_config_map.contains(name)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: Multiple configs with TRUE probe" |
| " have the same name: ", |
| name)); |
| } |
| auto& probed_data = probed_config_map[name]; |
| probed_data.fru_keys.push_back(FruKey(name)); |
| probed_data.config = config; |
| Fru entity_object; |
| entity_object.mutable_attributes()->set_key(name); |
| entity_object.mutable_attributes()->set_status(STATUS_READY); |
| // For Redfish Aggregation, mark the fru as not found for now |
| if (probe_str_view == kRedfishAggregationKeyword) { |
| entity_object.mutable_attributes()->set_status(STATUS_UNKNOWN); |
| } |
| fru_table.mutable_key_to_fru()->insert({name, entity_object}); |
| continue; |
| } |
| if (probe_str_view == "FALSE") { |
| continue; |
| } |
| // Store the config in the temporary map with key as FOUND probe |
| // and handle the config later. |
| if (absl::StartsWith(probe_str_view, "FOUND('") && |
| absl::EndsWith(probe_str_view, "')")) { |
| absl::string_view found_config_name = absl::StripSuffix( |
| absl::StripPrefix(probe_str_view, "FOUND('"), "')"); |
| ProbedConfigData probed_data; |
| probed_data.fru_keys.push_back(FruKey(name)); |
| probed_data.config = config; |
| pending_config_name_to_probed_configs[found_config_name].push_back( |
| probed_data); |
| continue; |
| } |
| } |
| nlohmann::json probe = *probe_result; |
| // Perform probe. |
| // Match Fru if config contains IPMI FRU probe. |
| auto ipmi_fru_it = probe.find("IpmiFru"); |
| if (ipmi_fru_it == probe.end()) { |
| return absl::InvalidArgumentError( |
| "Invalid config: IpmiFru field is not a valid probe"); |
| } |
| auto ipmi_fru_object = ipmi_fru_it->get<nlohmann::json>(); |
| absl::StatusOr<bool> is_fru_probed = |
| ProbeFru(ipmi_fru_object, config, name, fru_table, probed_config_map); |
| if (!is_fru_probed.ok()) { |
| return is_fru_probed.status(); |
| } |
| |
| if (!*is_fru_probed && probe.contains("AdHocFruConfig")) { |
| ad_hoc_fru_config_map[name] = config; |
| } |
| } |
| |
| // Post-process the configs with FOUND probe. |
| // Using topological sort (bfs) to find all the configs which FOUND probe |
| // rule is fulfilled. This is to avoid the recursion approach in |
| // https://github.com/openbmc/entity-manager/blob/7f51d32fb69f3bb6c4eabf685a27b57f345445cb/src/entity_manager/perform_scan.cpp#L582 |
| std::queue<std::string> found_target_candidates; |
| for (const auto& [name, _] : probed_config_map) { |
| if (pending_config_name_to_probed_configs.find(name) != |
| pending_config_name_to_probed_configs.end()) { |
| found_target_candidates.push(name); |
| } |
| } |
| while (!found_target_candidates.empty()) { |
| const std::string name = found_target_candidates.front(); |
| found_target_candidates.pop(); |
| auto found_config_it = pending_config_name_to_probed_configs.find(name); |
| if (found_config_it != pending_config_name_to_probed_configs.end()) { |
| for (const auto& found_config_data : found_config_it->second) { |
| const std::string config_name = |
| found_config_data.fru_keys[0].ToString(); |
| if (probed_config_map.contains(config_name) && |
| !absl::StrContains(name, "$index")) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: Multiple configs with FOUND probe" |
| " have the same name: ", |
| config_name)); |
| } |
| found_target_candidates.push(config_name); |
| probed_config_map[config_name] = found_config_data; |
| Fru entity_object; |
| entity_object.mutable_attributes()->set_key(config_name); |
| entity_object.mutable_attributes()->set_status(STATUS_READY); |
| fru_table.mutable_key_to_fru()->insert({config_name, entity_object}); |
| LOG(INFO) << "FOUND " << name << ", appending config " << config_name; |
| } |
| pending_config_name_to_probed_configs.erase(found_config_it); |
| } |
| } |
| processed_config_data.probed_config_map = std::move(probed_config_map); |
| processed_config_data.unscanned_ad_hoc_fru_configs = |
| std::move(ad_hoc_fru_config_map); |
| return processed_config_data; |
| } |
| |
| void SortChildrenResourceIds(TopologyConfig& topology_config) { |
| for (auto& [_, node] : *topology_config.mutable_topology_config_nodes()) { |
| std::sort(node.mutable_children_chassis_ids()->pointer_begin(), |
| node.mutable_children_chassis_ids()->pointer_end(), |
| [](const std::string* id_1, const std::string* id_2) { |
| return *id_1 < *id_2; |
| }); |
| std::sort(node.mutable_children_cable_ids()->pointer_begin(), |
| node.mutable_children_cable_ids()->pointer_end(), |
| [](const std::string* id_1, const std::string* id_2) { |
| return *id_1 < *id_2; |
| }); |
| std::sort(node.mutable_children_storage_ids()->pointer_begin(), |
| node.mutable_children_storage_ids()->pointer_end(), |
| [](const std::string* id_1, const std::string* id_2) { |
| return *id_1 < *id_2; |
| }); |
| } |
| } |
| |
| void SetDefaultRelatedItem(EntityCommonConfig& entity_common_config) { |
| // By default, if no related item is specified, the sensor related item |
| // will be the parent board of the config it is defined in. |
| RelatedItem default_related_board; |
| default_related_board.set_id(entity_common_config.board_config_key()); |
| default_related_board.set_type(RESOURCE_TYPE_BOARD); |
| *entity_common_config.mutable_related_item() = default_related_board; |
| } |
| |
| absl::StatusOr<const nlohmann::json::array_t> GetExposesElements( |
| const nlohmann::json& config, absl::string_view name) { |
| absl::StatusOr<nlohmann::json> exposes = GetObject(config, "Exposes"); |
| if (!exposes.ok()) { |
| return exposes.status(); |
| } |
| const nlohmann::json::array_t* elements = |
| exposes->get_ptr<const nlohmann::json::array_t*>(); |
| if (elements == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: The JSON field `Exposes` is not an array", name)); |
| } |
| return *elements; |
| } |
| |
| } // namespace |
| |
| template <class TypeName> |
| struct TypeComparator { |
| // Compared by the first element which is the type name. |
| bool operator()(const std::pair<std::string_view, TypeName>& lhs, |
| const std::pair<std::string_view, TypeName>& rhs) const { |
| return lhs.first < rhs.first; |
| } |
| }; |
| |
| struct TopologyConfigNodePtrComparator { |
| bool operator()(const TopologyConfigNode* lhs, |
| const TopologyConfigNode* rhs) const { |
| return lhs->location_context().devpath() > |
| rhs->location_context().devpath(); |
| } |
| }; |
| |
| constexpr std::array<std::pair<std::string_view, HwmonTempSensorType>, 4> |
| kSupportedHwmonTempSensorTypes = {{ |
| // go/keep-sorted start |
| {"MAX31725", HWMON_TEMP_SENSOR_TYPE1_MAX31725}, |
| {"MAX31732", HWMON_TEMP_SENSOR_TYPE3_MAX31732}, |
| {"SA56004", HWMON_TEMP_SENSOR_TYPE4_SA56004}, |
| {"TMP75", HWMON_TEMP_SENSOR_TYPE2_TMP75}, |
| // go/keep-sorted end |
| }}; |
| |
| constexpr std::array<std::pair<std::string_view, PsuSensorType>, 15> |
| kSupportedPsuSensorTypes = {{ |
| // go/keep-sorted start |
| {"ADM1266", PSU_SENSOR_TYPE1_ADM1266}, |
| {"ADM1272", PSU_SENSOR_TYPE2_ADM1272}, |
| {"LTC2991", PSU_SENSOR_TYPE4_LTC2991}, |
| {"LTC4287", PSU_SENSOR_TYPE9_LTC4287}, |
| {"LX6301", PSU_SENSOR_TYPE14_LX6301}, |
| {"Q50SN12072", PSU_SENSOR_TYPE10_Q50SN12072}, |
| {"Q54SN120A4", PSU_SENSOR_TYPE11_Q54SN120A4}, |
| {"RAA228228", PSU_SENSOR_TYPE5_RAA228228}, |
| {"RAA229639", PSU_SENSOR_TYPE13_RAA229639}, |
| {"SBTSI_I3C", PSU_SENSOR_TYPE15_SBTSI_I3C}, |
| {"TDA38725", PSU_SENSOR_TYPE6_TDA38725}, |
| {"TDA38740", PSU_SENSOR_TYPE7_TDA38740}, |
| {"TPS25990", PSU_SENSOR_TYPE12_TPS25990}, |
| {"XDPE1A2G5B", PSU_SENSOR_TYPE8_XDPE1A2G5B}, |
| {"pmbus", PSU_SENSOR_TYPE3_PMBUS}, |
| // go/keep-sorted end |
| }}; |
| |
| constexpr std::array<std::pair<std::string_view, FanControllerType>, 1> |
| kSupportedFanControllerTypes = {{ |
| // go/keep-sorted start |
| {"MAX31790", FAN_CONTROLLER_TYPE_MAX31790}, |
| // go/keep-sorted end |
| }}; |
| |
| constexpr std::array<std::pair<std::string_view, FanPwmType>, 1> |
| kSupportedFanPwmTypes = {{ |
| // go/keep-sorted start |
| {"I2CFan", PWM_SENSOR_TYPE_I2C_FAN}, |
| // go/keep-sorted end |
| }}; |
| |
| constexpr std::array<std::pair<std::string_view, FanTachType>, 1> |
| kSupportedFanTachTypes = {{ |
| // go/keep-sorted start |
| {"I2CFan", TACH_SENSOR_TYPE_I2C_FAN}, |
| // go/keep-sorted end |
| }}; |
| |
| constexpr std::array<std::pair<std::string_view, SharedMemSensorType>, 1> |
| kSupportedSharedMemSensorTypes = {{ |
| // go/keep-sorted start |
| {"SharedMemSensor", SHARED_MEM_SENSOR_TYPE_TYPE1_NUMERIC_SENSOR}, |
| // go/keep-sorted end |
| }}; |
| |
| constexpr std::array<std::pair<std::string_view, IntelCpuSensorType>, 1> |
| kSupportedIntelCpuSensorTypes = { |
| // go/keep-sorted start |
| {{"XeonCPU", INTEL_CPU_SENSOR_TYPE1_XEON_CPU}} |
| // go/keep-sorted end |
| }; |
| |
| // For each type of sensor config within our EntityConfig, we need to check if |
| // the sensor is associated with an assembly. To do so, we perform a lookup in |
| // the TopologyConfig and FruTable to check the resource type of the assigned |
| // board_config_key, if an Assembly, we propagate the sensor config to the |
| // parent. |
| // Assigned board_config_keys are guaranteed to be valid in the TopologyConfig, |
| // and fru_key in the topology config will be valid in the FruTable since these |
| // are where these values are set from. |
| void EntityConfigJsonImpl::PropagateSensorsFromAssemblyToBoard( |
| EntityConfigJsonImplImmutableData& data, |
| const TopologyConfig& topology_config, const FruTable& fru_table) { |
| for (auto& config : data.hwmon_temp_sensor_configs) { |
| auto node_it = topology_config.topology_config_nodes().find( |
| config.entity_common_config().board_config_key()); |
| |
| if (node_it != topology_config.topology_config_nodes().end()) { |
| auto fru_it = |
| fru_table.key_to_fru().find(node_it->second.fru_info().fru_key()); |
| |
| /// Propagate sensor config to parent board if the sensor is associated |
| // with an assembly. |
| if (fru_it != fru_table.key_to_fru().end() && |
| fru_it->second.attributes().resource_type() == |
| RESOURCE_TYPE_ASSEMBLY) { |
| *config.mutable_entity_common_config()->mutable_board_config_key() = |
| node_it->second.parent_resource_id(); |
| } |
| } |
| |
| // Always set default related item if not specified |
| if (!config.entity_common_config().has_related_item()) { |
| SetDefaultRelatedItem(*config.mutable_entity_common_config()); |
| } |
| } |
| |
| for (auto& config : data.psu_sensor_configs) { |
| auto node_it = topology_config.topology_config_nodes().find( |
| config.entity_common_config().board_config_key()); |
| |
| if (node_it != topology_config.topology_config_nodes().end()) { |
| auto fru_it = |
| fru_table.key_to_fru().find(node_it->second.fru_info().fru_key()); |
| |
| /// Propagate sensor config to parent board if the sensor is associated |
| // with an assembly. |
| if (fru_it != fru_table.key_to_fru().end() && |
| fru_it->second.attributes().resource_type() == |
| RESOURCE_TYPE_ASSEMBLY) { |
| *config.mutable_entity_common_config()->mutable_board_config_key() = |
| node_it->second.parent_resource_id(); |
| } |
| } |
| |
| // Always set default related item if not specified |
| if (!config.entity_common_config().has_related_item()) { |
| SetDefaultRelatedItem(*config.mutable_entity_common_config()); |
| } |
| } |
| |
| for (auto& config : data.fan_pwm_configs) { |
| auto node_it = topology_config.topology_config_nodes().find( |
| config.entity_common_config().board_config_key()); |
| |
| if (node_it != topology_config.topology_config_nodes().end()) { |
| auto fru_it = |
| fru_table.key_to_fru().find(node_it->second.fru_info().fru_key()); |
| |
| /// Propagate sensor config to parent board if the sensor is associated |
| // with an assembly. |
| if (fru_it != fru_table.key_to_fru().end() && |
| fru_it->second.attributes().resource_type() == |
| RESOURCE_TYPE_ASSEMBLY) { |
| *config.mutable_entity_common_config()->mutable_board_config_key() = |
| node_it->second.parent_resource_id(); |
| } |
| } |
| } |
| |
| for (auto& config : data.fan_tach_configs) { |
| auto node_it = topology_config.topology_config_nodes().find( |
| config.entity_common_config().board_config_key()); |
| |
| if (node_it != topology_config.topology_config_nodes().end()) { |
| auto fru_it = |
| fru_table.key_to_fru().find(node_it->second.fru_info().fru_key()); |
| |
| /// Propagate sensor config to parent board if the sensor is associated |
| // with an assembly. |
| if (fru_it != fru_table.key_to_fru().end() && |
| fru_it->second.attributes().resource_type() == |
| RESOURCE_TYPE_ASSEMBLY) { |
| *config.mutable_entity_common_config()->mutable_board_config_key() = |
| node_it->second.parent_resource_id(); |
| } |
| } |
| } |
| |
| for (auto& config : data.shared_mem_sensor_configs) { |
| auto node_it = topology_config.topology_config_nodes().find( |
| config.entity_common_config().board_config_key()); |
| |
| if (node_it != topology_config.topology_config_nodes().end()) { |
| auto fru_it = |
| fru_table.key_to_fru().find(node_it->second.fru_info().fru_key()); |
| |
| /// Propagate sensor config to parent board if the sensor is associated |
| // with an assembly. |
| if (fru_it != fru_table.key_to_fru().end() && |
| fru_it->second.attributes().resource_type() == |
| RESOURCE_TYPE_ASSEMBLY) { |
| *config.mutable_entity_common_config()->mutable_board_config_key() = |
| node_it->second.parent_resource_id(); |
| } |
| } |
| } |
| } |
| |
| HwmonTempSensorType EntityConfigJsonImpl::IsHwmonTempSensor( |
| std::string_view type) { |
| const auto* it = |
| std::lower_bound(kSupportedHwmonTempSensorTypes.begin(), |
| kSupportedHwmonTempSensorTypes.end(), |
| std::pair<std::string_view, HwmonTempSensorType>{ |
| type, HWMON_TEMP_SENSOR_TYPE0_UNKNOWN}, |
| TypeComparator<HwmonTempSensorType>()); |
| if (it == kSupportedHwmonTempSensorTypes.end() || it->first != type) { |
| return HWMON_TEMP_SENSOR_TYPE0_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| PsuSensorType EntityConfigJsonImpl::IsPsuSensor(std::string_view type) { |
| const auto* it = std::lower_bound(kSupportedPsuSensorTypes.begin(), |
| kSupportedPsuSensorTypes.end(), |
| std::pair<std::string_view, PsuSensorType>{ |
| type, PSU_SENSOR_TYPE0_UNKNOWN}, |
| TypeComparator<PsuSensorType>()); |
| if (it == kSupportedPsuSensorTypes.end() || it->first != type) { |
| return PSU_SENSOR_TYPE0_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| FanControllerType EntityConfigJsonImpl::IsFanController(std::string_view type) { |
| const auto* it = std::lower_bound( |
| kSupportedFanControllerTypes.begin(), kSupportedFanControllerTypes.end(), |
| std::pair<std::string_view, FanControllerType>{ |
| type, FAN_CONTROLLER_TYPE_UNKNOWN}, |
| TypeComparator<FanControllerType>()); |
| if (it == kSupportedFanControllerTypes.end() || it->first != type) { |
| return FAN_CONTROLLER_TYPE_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| FanPwmType EntityConfigJsonImpl::IsFanPwm(std::string_view type) { |
| const auto* it = std::lower_bound( |
| kSupportedFanPwmTypes.begin(), kSupportedFanPwmTypes.end(), |
| std::pair<std::string_view, FanPwmType>{type, PWM_SENSOR_TYPE_UNKNOWN}, |
| TypeComparator<FanPwmType>()); |
| if (it == kSupportedFanPwmTypes.end() || it->first != type) { |
| return PWM_SENSOR_TYPE_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| FanTachType EntityConfigJsonImpl::IsFanTach(std::string_view type) { |
| const auto* it = std::lower_bound( |
| kSupportedFanTachTypes.begin(), kSupportedFanTachTypes.end(), |
| std::pair<std::string_view, FanTachType>{type, TACH_SENSOR_TYPE_UNKNOWN}, |
| TypeComparator<FanTachType>()); |
| if (it == kSupportedFanTachTypes.end() || it->first != type) { |
| return TACH_SENSOR_TYPE_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| SharedMemSensorType EntityConfigJsonImpl::IsSharedMemSensor( |
| std::string_view type) { |
| const auto* it = |
| std::lower_bound(kSupportedSharedMemSensorTypes.begin(), |
| kSupportedSharedMemSensorTypes.end(), |
| std::pair<std::string_view, SharedMemSensorType>{ |
| type, SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN}, |
| TypeComparator<SharedMemSensorType>()); |
| if (it == kSupportedSharedMemSensorTypes.end() || it->first != type) { |
| return SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| IntelCpuSensorType EntityConfigJsonImpl::IsIntelCpuSensor( |
| std::string_view type) { |
| const auto* it = |
| std::lower_bound(kSupportedIntelCpuSensorTypes.begin(), |
| kSupportedIntelCpuSensorTypes.end(), |
| std::pair<std::string_view, IntelCpuSensorType>{ |
| type, INTEL_CPU_SENSOR_TYPE0_UNKNOWN}, |
| TypeComparator<IntelCpuSensorType>()); |
| if (it == kSupportedIntelCpuSensorTypes.end() || it->first != type) { |
| return INTEL_CPU_SENSOR_TYPE0_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| bool EntityConfigJsonImpl::IsVirtualSensor(std::string_view type) { |
| return type == "VirtualSensor"; |
| } |
| |
| bool EntityConfigJsonImpl::IsFan(std::string_view type) { |
| return type == "Fan"; |
| } |
| |
| bool EntityConfigJsonImpl::IsNicTelemetry(std::string_view type) { |
| return type == "NicTelemetry"; |
| } |
| |
| bool EntityConfigJsonImpl::IsDimm(std::string_view type) { |
| return type == kDimmKeyword; |
| } |
| |
| absl::Status EntityConfigJsonImpl::ParseAndPopulateConfig( |
| EntityConfigJsonImplImmutableData& data, const nlohmann::json& element, |
| absl::string_view config_name_with_index, bool is_subfru, |
| bool is_detected) { |
| const bool* tlbmc_owned = GetValueAsBool(element, "TlbmcOwned"); |
| if (tlbmc_owned == nullptr || !*tlbmc_owned) { |
| return absl::OkStatus(); |
| } |
| bool tlbmc_supported = false; |
| const std::string* type = GetValueAsString(element, "Type"); |
| if (type == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a string"); |
| } |
| // HWMon temperature sensor. |
| HwmonTempSensorType hwmon_temp_sensor_type = IsHwmonTempSensor(*type); |
| if (hwmon_temp_sensor_type != HWMON_TEMP_SENSOR_TYPE0_UNKNOWN) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN(HwmonTempSensorConfig hwmon_temp_sensor_config, |
| ParseHwmonTempSensorConfig(hwmon_temp_sensor_type, |
| element, is_detected)); |
| hwmon_temp_sensor_config.mutable_entity_common_config() |
| ->set_board_config_key(config_name_with_index); |
| data.hwmon_temp_sensor_configs.push_back( |
| std::move(hwmon_temp_sensor_config)); |
| } |
| |
| // PSU sensor. |
| PsuSensorType psu_sensor_type = IsPsuSensor(*type); |
| if (psu_sensor_type != PSU_SENSOR_TYPE0_UNKNOWN) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN( |
| PsuSensorConfig psu_sensor_config, |
| ParsePsuSensorConfig(psu_sensor_type, element, is_detected)); |
| psu_sensor_config.mutable_entity_common_config()->set_board_config_key( |
| config_name_with_index); |
| data.psu_sensor_configs.push_back(std::move(psu_sensor_config)); |
| } |
| |
| // FAN controller |
| FanControllerType fan_controller_type = IsFanController(*type); |
| if (fan_controller_type != FAN_CONTROLLER_TYPE_UNKNOWN) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN( |
| FanControllerConfig fan_controller_config, |
| ParseFanControllerConfig(fan_controller_type, element, is_detected)); |
| data.fan_controller_configs.push_back(std::move(fan_controller_config)); |
| } |
| |
| FanPwmType fan_pwm_type = IsFanPwm(*type); |
| if (fan_pwm_type != PWM_SENSOR_TYPE_UNKNOWN) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN( |
| FanPwmConfig fan_pwm_config, |
| ParseFanPwmConfig(fan_pwm_type, element, is_detected)); |
| fan_pwm_config.mutable_entity_common_config()->set_board_config_key( |
| config_name_with_index); |
| data.fan_pwm_configs.push_back(std::move(fan_pwm_config)); |
| } |
| |
| FanTachType fan_tach_type = IsFanTach(*type); |
| if (fan_tach_type != TACH_SENSOR_TYPE_UNKNOWN) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN( |
| FanTachConfig fan_tach_config, |
| ParseFanTachConfig(fan_tach_type, element, is_detected)); |
| fan_tach_config.mutable_entity_common_config()->set_board_config_key( |
| config_name_with_index); |
| data.fan_tach_configs.push_back(std::move(fan_tach_config)); |
| } |
| |
| SharedMemSensorType shared_mem_sensor_type = IsSharedMemSensor(*type); |
| if (shared_mem_sensor_type != SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN(SharedMemSensorConfig shared_mem_sensor_config, |
| ParseSharedMemSensorConfig(shared_mem_sensor_type, |
| element, is_detected)); |
| shared_mem_sensor_config.mutable_entity_common_config() |
| ->set_board_config_key(config_name_with_index); |
| data.shared_mem_sensor_configs.push_back( |
| std::move(shared_mem_sensor_config)); |
| } |
| |
| IntelCpuSensorType intel_cpu_sensor_type = IsIntelCpuSensor(*type); |
| if (intel_cpu_sensor_type != INTEL_CPU_SENSOR_TYPE0_UNKNOWN) { |
| tlbmc_supported = true; |
| absl::StatusOr<IntelCpuSensorConfig> intel_cpu_sensor_config = |
| ParseIntelCpuSensorConfig(intel_cpu_sensor_type, element, is_detected); |
| if (!intel_cpu_sensor_config.ok()) { |
| return intel_cpu_sensor_config.status(); |
| } |
| intel_cpu_sensor_config->mutable_entity_common_config() |
| ->set_board_config_key(config_name_with_index); |
| data.intel_cpu_sensor_configs.push_back(*intel_cpu_sensor_config); |
| } |
| |
| if (IsVirtualSensor(*type)) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN(VirtualSensorConfig virtual_sensor_config, |
| ParseVirtualSensorConfig(element, is_detected)); |
| virtual_sensor_config.mutable_entity_common_config()->set_board_config_key( |
| config_name_with_index); |
| data.virtual_sensor_configs.push_back(std::move(virtual_sensor_config)); |
| } |
| |
| // We support these types but they need to be parsed later. |
| if (IsFan(*type) || IsDimm(*type)) { |
| tlbmc_supported = true; |
| } |
| |
| if (IsNicTelemetry(*type)) { |
| tlbmc_supported = true; |
| ECCLESIA_ASSIGN_OR_RETURN(NicTelemetryConfig nic_telemetry_config, |
| ParseNicTelemetryConfig(element, is_detected)); |
| *nic_telemetry_config.mutable_entity_common_config() |
| ->mutable_board_config_key() = config_name_with_index; |
| data.nic_telemetry_configs.push_back(std::move(nic_telemetry_config)); |
| } |
| |
| if (!tlbmc_supported) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: Config is TlbmcOwned but does not match any supported " |
| "sensor type ", |
| *type)); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| void EntityConfigJsonImpl::UpdateFruAndTopology(const RawFruTable& fru_table, |
| const RawFru& raw_fru) { |
| EntityConfigJsonImplData new_data = ReloadConfig( |
| fru_table, 0, |
| /*reload_fru_and_topology_only=*/ReloadType::kReloadFruAndTopologyOnly); |
| |
| if (!new_data.mutable_data.parsed_status.ok()) { |
| LOG(WARNING) << "Failed to parse config: " |
| << new_data.mutable_data.parsed_status; |
| } |
| |
| // Update the mutable data. Should not fail. |
| absl::Status update_status = |
| data_store_.mutable_data.Update(std::move(new_data.mutable_data)); |
| if (!update_status.ok()) { |
| LOG(WARNING) << "Failed to update FRU and topology: " << update_status; |
| } |
| |
| if (smart_router_ != nullptr) { |
| smart_router_->UpdateTlbmcOwnedUrls(); |
| } |
| |
| if (sensor_collector_ != nullptr) { |
| absl::StatusOr<std::string> config_key = |
| GetConfigKeyByFruKey(raw_fru.key()); |
| if (!config_key.ok()) { |
| LOG(WARNING) << "Failed to get config key by FRU key: " |
| << config_key.status() |
| << ". Cannot reinitialize sensors for fru: " |
| << raw_fru.key(); |
| return; |
| } |
| sensor_collector_->ReinitializeAndScheduleAllSensorsForConfigKey( |
| *config_key); |
| } |
| } |
| |
| EntityConfigJsonImplData EntityConfigJsonImpl::ReloadConfig( |
| const RawFruTable& fru_table, size_t ad_hoc_fru_count, |
| ReloadType reload_type) { |
| EntityConfigJsonImplImmutableData immutable_data; |
| EntityConfigJsonImplMutableData mutable_data; |
| |
| // Make Readonly copy of the FRU table. |
| for (const auto& [key, raw_fru] : fru_table.key_to_raw_fru()) { |
| Fru fru; |
| Attributes* attributes = fru.mutable_attributes(); |
| attributes->set_key(raw_fru.key()); |
| attributes->set_status(Status::STATUS_READY); |
| attributes->mutable_refresh_policy()->set_refresh_mode( |
| RefreshMode::REFRESH_MODE_ON_DEMAND); |
| |
| *fru.mutable_data() = raw_fru.data(); |
| *fru.mutable_i2c_info() = raw_fru.i2c_info(); |
| mutable_data.fru_table.mutable_key_to_fru()->insert({key, fru}); |
| } |
| |
| absl::StatusOr<ProcessedConfigData> processed_config_data = |
| ProcessConfigs(immutable_config_list_, mutable_data.fru_table); |
| if (!processed_config_data.ok()) { |
| mutable_data.parsed_status = processed_config_data.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| if (processed_config_data->probed_config_map.empty()) { |
| mutable_data.parsed_status = |
| absl::InvalidArgumentError("Probe failed for all configs"); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| // This map is used to update the FRU info for it's sub FRUs. |
| // Example: |
| // {"fru_key_1": {"sub_fru_config_1", "sub_fru_config_2"}, |
| // "fru_key_2": {"sub_fru_config_3"}} |
| absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>> |
| fru_key_to_sub_fru_config; |
| |
| // If multiple FRUs match the config, we need to substitute the FRU |
| // specific fields in the config with the FRU info and then parse the |
| // config. |
| for (const auto& [name, config_data] : |
| processed_config_data->probed_config_map) { |
| // Check if config belongs to a sub FRU. |
| // RESOURCE_TYPE_ASSEMBLY indicates a config that is a sub FRU. |
| absl::StatusOr<ResourceType> resource_type = |
| ParseResourceType(config_data.config); |
| |
| if (!resource_type.ok()) { |
| mutable_data.parsed_status = resource_type.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| // For each FRU, substitute config variables with FRU info and parse the |
| // config. |
| // `ProcessConfigs` is already checked to guarantee that only configs with |
| // $index will have multiple Fru keys matching. |
| for (size_t i = 0; i < config_data.fru_keys.size(); i++) { |
| // Create a new topology config node for each FRU. |
| TopologyConfigNode topology_config_node; |
| std::string config_name_with_index = name; |
| absl::StrReplaceAll({{"$index", std::to_string(i + 1)}}, |
| &config_name_with_index); |
| topology_config_node.set_name(config_name_with_index); |
| |
| std::string fru_key = config_data.fru_keys[i].ToString(); |
| |
| // In the case of a sub FRU, we create an entry in the FruTable keyed by |
| // the config name to have separate Fru data from the parent Fru. |
| bool is_subfru = *resource_type == RESOURCE_TYPE_ASSEMBLY; |
| if (is_subfru) { |
| fru_key = config_name_with_index; |
| Fru sub_fru_info; |
| Attributes* attributes = sub_fru_info.mutable_attributes(); |
| attributes->set_key(fru_key); |
| attributes->set_status(Status::STATUS_READY); |
| attributes->mutable_refresh_policy()->set_refresh_mode( |
| RefreshMode::REFRESH_MODE_ON_DEMAND); |
| mutable_data.fru_table.mutable_key_to_fru()->insert( |
| {fru_key, sub_fru_info}); |
| } |
| |
| // Get the FRU object from the fru_key. This is used to update the Fru |
| // object fields with Fru information from config. |
| // Key is not expected to be invalid. |
| Fru& fru = mutable_data.fru_table.mutable_key_to_fru()->at(fru_key); |
| |
| fru.mutable_attributes()->set_resource_type(*resource_type); |
| |
| if (*resource_type == RESOURCE_TYPE_BOARD) { |
| fru.mutable_attributes() |
| ->mutable_chassis_properties() |
| ->set_chassis_type(ParseChassisType(config_data.config)); |
| } |
| |
| ParsePartLocationType(config_data.config, topology_config_node); |
| ParseRootChassisLocationCode(config_data.config, topology_config_node); |
| |
| absl::StatusOr<const nlohmann::json::array_t> elements = |
| GetExposesElements(config_data.config, name); |
| if (!elements.ok()) { |
| mutable_data.parsed_status = elements.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| // Get the FRU info for the FRU key. This is used to substitute the FRU |
| // specific fields in the config. |
| // Key is not expected to be invalid. |
| const Fru& fru_info = mutable_data.fru_table.key_to_fru().at(fru_key); |
| for (const auto& element_before_fru_substitution : *elements) { |
| // Substitute FRU specific fields. |
| nlohmann::json element = element_before_fru_substitution; |
| |
| // Substitute Bus field. |
| const std::string* config_bus = GetValueAsString(element, "Bus"); |
| if (config_bus != nullptr && fru_info.has_i2c_info() && |
| absl::StrContainsIgnoreCase(*config_bus, "$bus")) { |
| absl::string_view config_bus_view(*config_bus); |
| absl::StatusOr<std::string> bus_value_substitute_result = |
| EvaluateStrExpressionSubstitution( |
| config_bus_view, |
| absl::StrContains(config_bus_view, "$bus") ? "$bus" : "$BUS", |
| absl::StrCat(fru_info.i2c_info().bus())); |
| if (!bus_value_substitute_result.ok()) { |
| mutable_data.parsed_status = bus_value_substitute_result.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| uint64_t bus_value; |
| if (!absl::SimpleAtoi(*bus_value_substitute_result, &bus_value)) { |
| LOG(WARNING) << absl::StrCat( |
| "Invalid json: Failed to convert value at Bus", |
| " to unsigned value: ", config_bus_view); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| element["Bus"] = bus_value; |
| } |
| |
| // Substitute Address field. |
| const std::string* config_address_str = |
| GetValueAsString(element, "Address"); |
| if (config_address_str != nullptr && |
| absl::StrContainsIgnoreCase(*config_address_str, "$address") && |
| fru_info.has_i2c_info()) { |
| element["Address"] = |
| absl::StrCat(absl::Hex(fru_info.i2c_info().address())); |
| } |
| |
| // Substitute $bus and $index in values of all remaining elements in the |
| // config. |
| for (auto& [key, field_value] : element.items()) { |
| const std::string* field_value_str = GetValueAsString(element, key); |
| // Special case for RelatedItem, we know this will be nested and want |
| // substitution for it. |
| if (key == "RelatedItem") { |
| const std::string* item_id = GetValueAsString(field_value, "Id"); |
| if (item_id != nullptr) { |
| field_value["Id"] = absl::StrReplaceAll( |
| *item_id, {{"$index", absl::StrCat(i + 1)}}); |
| } |
| } |
| // If the field is not a string, continue since we expect substitution |
| // only for string fields. |
| if (field_value_str == nullptr) { |
| continue; |
| } |
| // Make a copy of the field value string since we will be modifying |
| // it in place. |
| std::string field_value_str_copy = *field_value_str; |
| if (absl::StrContainsIgnoreCase(field_value_str_copy, "$bus")) { |
| absl::StatusOr<std::string> field_value_substitute_result = |
| EvaluateStrExpressionSubstitution( |
| field_value_str_copy, |
| absl::StrContains(field_value_str_copy, "$bus") ? "$bus" |
| : "$BUS", |
| absl::StrCat(fru_info.i2c_info().bus())); |
| if (!field_value_substitute_result.ok()) { |
| mutable_data.parsed_status = |
| field_value_substitute_result.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| element[key] = *field_value_substitute_result; |
| } |
| |
| if (absl::StrContains(field_value_str_copy, "$index")) { |
| absl::StatusOr<std::string> field_value_substitute_result = |
| EvaluateStrExpressionSubstitution( |
| field_value_str_copy, "$index", absl::StrCat(i + 1)); |
| if (!field_value_substitute_result.ok()) { |
| mutable_data.parsed_status = |
| field_value_substitute_result.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| element[key] = *field_value_substitute_result; |
| } |
| } |
| |
| if (reload_type == ReloadType::kReloadAll) { |
| absl::Status config_parse_status = ParseAndPopulateConfig( |
| immutable_data, element, config_name_with_index, is_subfru, true); |
| if (!config_parse_status.ok()) { |
| mutable_data.parsed_status = config_parse_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| } |
| |
| absl::Status parse_port_config_status = |
| ParsePortConfig(element, topology_config_node); |
| if (!parse_port_config_status.ok()) { |
| mutable_data.parsed_status = parse_port_config_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| absl::Status parse_processor_status = |
| ParseProcessorConfig(mutable_data, element, topology_config_node, |
| config_name_with_index); |
| if (!parse_processor_status.ok()) { |
| mutable_data.parsed_status = parse_processor_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| absl::Status parse_dimm_status = |
| ParseDimmConfig(mutable_data, element, topology_config_node, |
| config_name_with_index); |
| if (!parse_dimm_status.ok()) { |
| mutable_data.parsed_status = parse_dimm_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| absl::Status parse_fan_status = |
| ParseFanConfig(mutable_data, element, topology_config_node, |
| config_name_with_index); |
| if (!parse_fan_status.ok()) { |
| mutable_data.parsed_status = parse_fan_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| if (fru_info.attributes().resource_type() == RESOURCE_TYPE_BOARD) { |
| ParseBmcNetConfig(element, fru); |
| } |
| } |
| |
| absl::Status parse_storage_status = |
| ParseStorageConfig(config_data.config, topology_config_node, i); |
| if (!parse_storage_status.ok()) { |
| mutable_data.parsed_status = parse_storage_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| absl::Status parse_asset_config_status = |
| ParseAssetConfig(config_data.config, fru); |
| if (!parse_asset_config_status.ok()) { |
| mutable_data.parsed_status = parse_asset_config_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| topology_config_node.mutable_fru_info()->set_fru_key(fru_key); |
| |
| mutable_data.topology_config.mutable_fru_configs()->insert( |
| {fru_key, config_name_with_index}); |
| if (is_subfru) { |
| topology_config_node.mutable_fru_info()->set_is_sub_fru(true); |
| fru_key_to_sub_fru_config[config_data.fru_keys[i].ToString()].insert( |
| config_name_with_index); |
| } |
| |
| // tlbmc does not own assemblies now. |
| if ((!GetTlbmcConfig().fru_collector_module().own_cables_in_redfish() && |
| fru_info.attributes().resource_type() == RESOURCE_TYPE_CABLE) || |
| fru_info.attributes().resource_type() == RESOURCE_TYPE_ASSEMBLY) { |
| topology_config_node.set_not_owned_by_tlbmc(true); |
| } |
| |
| // For now, tlBMC doesn't own redfish aggregated FRUs |
| if (fru_info.attributes().status() == STATUS_UNKNOWN) { |
| topology_config_node.set_not_owned_by_tlbmc(true); |
| } |
| |
| // If the config has port configs, add the topology config node to the |
| // topology config. |
| if (!topology_config_node.upstream_port_configs().empty() || |
| !topology_config_node.port_configs().empty()) { |
| topology_config_node.set_config_key(config_name_with_index); |
| mutable_data.topology_config.mutable_topology_config_nodes()->insert( |
| {std::move(config_name_with_index), |
| std::move(topology_config_node)}); |
| } |
| } |
| } |
| |
| // Update the FRU info for sub FRUs. |
| for (auto& [fru_key, config_keys] : fru_key_to_sub_fru_config) { |
| for (const auto& config_key : config_keys) { |
| auto config_key_it = |
| mutable_data.topology_config.mutable_fru_configs()->find(fru_key); |
| if (config_key_it == |
| mutable_data.topology_config.mutable_fru_configs()->end()) { |
| mutable_data.parsed_status = absl::InternalError( |
| absl::StrCat("Fru config not found for Sub FRU defined in config: ", |
| config_key)); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| // Topology config is expected to be present in the map as the name is |
| // obtained from the map itself. |
| TopologyConfigNode& topology_config_node = |
| mutable_data.topology_config.mutable_topology_config_nodes()->at( |
| config_key_it->second); |
| *topology_config_node.mutable_sub_fru_config_keys()->Add() = config_key; |
| } |
| } |
| |
| absl::flat_hash_set<absl::string_view> fru_keys_with_sub_frus; |
| for (const auto& [fru_key, _] : mutable_data.fru_table.key_to_fru()) { |
| auto it = mutable_data.topology_config.mutable_fru_configs()->find(fru_key); |
| if (it == mutable_data.topology_config.mutable_fru_configs()->end()) { |
| if (!GetTlbmcConfig().fru_collector_module().allow_dangling_frus()) { |
| mutable_data.parsed_status = absl::InternalError(absl::StrCat( |
| "Invalid topology: no config with matching probe found for FRU: ", |
| fru_key)); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| continue; |
| } |
| fru_keys_with_sub_frus.insert(it->second); |
| } |
| for (const auto& [fru_key, sub_fru_config_keys] : fru_key_to_sub_fru_config) { |
| fru_keys_with_sub_frus.insert(sub_fru_config_keys.begin(), |
| sub_fru_config_keys.end()); |
| } |
| |
| absl::Status create_associations_status = |
| CreateAssociationsBetweenTopologyConfigNodes(mutable_data.topology_config, |
| mutable_data.fru_table, |
| fru_keys_with_sub_frus); |
| if (!create_associations_status.ok()) { |
| mutable_data.parsed_status = create_associations_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| // Sort the children of each Fru |
| SortChildrenResourceIds(mutable_data.topology_config); |
| |
| // Parse sensors for AdHoc FRUs. |
| if (reload_type == ReloadType::kReloadAll) { |
| for (const auto& [name, config] : |
| processed_config_data->unscanned_ad_hoc_fru_configs) { |
| absl::StatusOr<const nlohmann::json::array_t> elements = |
| GetExposesElements(config, name); |
| if (!elements.ok()) { |
| mutable_data.parsed_status = elements.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| for (const auto& element : *elements) { |
| absl::Status config_parse_status = |
| ParseAndPopulateConfig(immutable_data, element, name, false, false); |
| if (!config_parse_status.ok()) { |
| mutable_data.parsed_status = config_parse_status; |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| } |
| } |
| } |
| |
| // Propagate sensors defined in Assembly to their parent boards |
| PropagateSensorsFromAssemblyToBoard( |
| immutable_data, mutable_data.topology_config, mutable_data.fru_table); |
| |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| |
| ReadingRangeConfigs EntityConfigJsonImpl::ParseReadingRangeConfigs( |
| const nlohmann::json& config, std::optional<double> default_min, |
| std::optional<double> default_max) { |
| ReadingRangeConfigs reading_range_configs; |
| std::optional<double> max = |
| GetValueAsDoubleFromFloatOrInteger(config, "MaxReading"); |
| if (max.has_value() || default_max) { |
| ReadingRangeConfig max_range; |
| max_range.set_type(READING_RANGE_TYPE_MAX); |
| max_range.set_reading(!max.has_value() ? *default_max : *max); |
| *reading_range_configs.mutable_reading_range_configs()->Add() = max_range; |
| } |
| std::optional<double> min = |
| GetValueAsDoubleFromFloatOrInteger(config, "MinReading"); |
| if (min.has_value() || default_min) { |
| ReadingRangeConfig min_range; |
| min_range.set_type(READING_RANGE_TYPE_MIN); |
| min_range.set_reading(!min.has_value() ? *default_min : *min); |
| *reading_range_configs.mutable_reading_range_configs()->Add() = min_range; |
| } |
| return reading_range_configs; |
| } |
| |
| absl::flat_hash_map<std::string, ReadingRangeConfigs> |
| EntityConfigJsonImpl::ParseReadingRangeConfigs( |
| const nlohmann::json& config, absl::Span<const std::string> labels) { |
| absl::flat_hash_map<std::string, ReadingRangeConfigs> |
| label_to_reading_range_configs; |
| for (const std::string& label : labels) { |
| std::string max_key = absl::Substitute("$0_Max", label); |
| std::optional<double> max = |
| GetValueAsDoubleFromFloatOrInteger(config, max_key); |
| if (max.has_value()) { |
| ReadingRangeConfig max_range; |
| max_range.set_type(READING_RANGE_TYPE_MAX); |
| max_range.set_reading(*max); |
| ReadingRangeConfigs& max_configs = label_to_reading_range_configs[label]; |
| *max_configs.mutable_reading_range_configs()->Add() = max_range; |
| } |
| |
| std::string min_key = absl::Substitute("$0_Min", label); |
| std::optional<double> min = |
| GetValueAsDoubleFromFloatOrInteger(config, min_key); |
| if (min.has_value()) { |
| ReadingRangeConfig min_range; |
| min_range.set_type(READING_RANGE_TYPE_MIN); |
| min_range.set_reading(*min); |
| ReadingRangeConfigs& min_configs = label_to_reading_range_configs[label]; |
| *min_configs.mutable_reading_range_configs()->Add() = min_range; |
| } |
| } |
| return label_to_reading_range_configs; |
| } |
| |
| absl::flat_hash_map<std::string, ReadingTransformConfig> |
| EntityConfigJsonImpl::ParseReadingTransformConfigs( |
| const nlohmann::json& config, absl::Span<const std::string> labels) { |
| absl::flat_hash_map<std::string, ReadingTransformConfig> |
| label_to_reading_transform; |
| for (const std::string& label : labels) { |
| std::string scale_key = absl::Substitute("$0_Scale", label); |
| std::optional<double> scale = |
| GetValueAsDoubleFromFloatOrInteger(config, scale_key); |
| std::string offset_key = absl::Substitute("$0_Offset", label); |
| std::optional<double> offset = |
| GetValueAsDoubleFromFloatOrInteger(config, offset_key); |
| ReadingTransformConfig reading_transform_config; |
| // Only allow custom scale factor > 0 to avoid division by zero |
| // https://github.com/openbmc/dbus-sensors/blob/af1724b84b7558037c665d2106ec44d7362aca6b/src/psu/PSUSensorMain.cpp#L738 |
| if (scale.has_value() && *scale > 0) { |
| reading_transform_config.set_scale(*scale); |
| } |
| if (offset.has_value()) { |
| reading_transform_config.set_offset(*offset); |
| } |
| if (reading_transform_config.has_scale() || |
| reading_transform_config.has_offset()) { |
| label_to_reading_transform[label] = reading_transform_config; |
| } |
| } |
| return label_to_reading_transform; |
| } |
| |
| absl::StatusOr<HalCommonConfig> EntityConfigJsonImpl::ParseHalCommonConfig( |
| const nlohmann::json& config) { |
| HalCommonConfig hal_config; |
| std::optional<uint64_t> bus = |
| GetValueAsUintFromStringOrInteger(config, "Bus"); |
| if (!bus.has_value()) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: element does not have a Bus field: ", config.dump())); |
| } |
| hal_config.set_bus(*bus); |
| |
| const std::string* address = GetValueAsString(config, "Address"); |
| if (address == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: element does not have an Address field"); |
| } |
| |
| uint64_t address_uint64; |
| if (!absl::SimpleHexAtoi(*address, &address_uint64)) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Address field is not a valid hex number: "); |
| } |
| hal_config.set_address(address_uint64); |
| return hal_config; |
| } |
| |
| absl::StatusOr<std::vector<std::string>> EntityConfigJsonImpl::ParseLabels( |
| const nlohmann::json& config) { |
| const nlohmann::json::array_t* labels = GetValueAsArray(config, "Labels"); |
| if (labels == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Labels field is not an array"); |
| } |
| std::vector<std::string> labels_vector; |
| for (const auto& label : *labels) { |
| const std::string* label_str = label.get_ptr<const std::string*>(); |
| if (label_str == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: label element is not a string"); |
| } |
| labels_vector.push_back(*label_str); |
| } |
| return labels_vector; |
| } |
| |
| absl::StatusOr<std::vector<nic_veeprom::NicTelemetryName>> |
| EntityConfigJsonImpl::ParseNicTelemetryNames(const nlohmann::json& config) { |
| const nlohmann::json::array_t* telemetry_names = |
| GetValueAsArray(config, "TelemetryNames"); |
| if (telemetry_names == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: TelemetryNames field is not an array"); |
| } |
| std::vector<nic_veeprom::NicTelemetryName> telemetry_names_vector; |
| for (const auto& telemetry_name : *telemetry_names) { |
| const std::string* telemetry_name_str = |
| telemetry_name.get_ptr<const std::string*>(); |
| if (telemetry_name_str == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: TelemetryNames element is not a string"); |
| } |
| nic_veeprom::NicTelemetryName telemetry_name_enum; |
| if (!nic_veeprom::NicTelemetryName_Parse(*telemetry_name_str, |
| &telemetry_name_enum)) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: TelemetryNames element is not a valid enum: ", |
| *telemetry_name_str)); |
| } |
| telemetry_names_vector.push_back(telemetry_name_enum); |
| } |
| return telemetry_names_vector; |
| } |
| |
| SensorUnit EntityConfigJsonImpl::ParseSensorUnit(const nlohmann::json& config) { |
| const std::string* unit_str = GetValueAsString(config, "Unit"); |
| if (unit_str == nullptr) { |
| LOG(INFO) << "No unit found for: " << config.dump() |
| << " setting to unknown"; |
| return SensorUnit::UNIT_UNKNOWN; |
| } |
| |
| const auto* it = std::lower_bound( |
| kSupportedSensorUnits.begin(), kSupportedSensorUnits.end(), |
| std::pair<std::string_view, SensorUnit>{*unit_str, |
| SensorUnit::UNIT_UNKNOWN}, |
| [](const auto& a, const auto& b) { return a.first < b.first; }); |
| if (it == kSupportedSensorUnits.end() || it->first != *unit_str) { |
| return SensorUnit::UNIT_UNKNOWN; |
| } |
| return it->second; |
| } |
| |
| absl::StatusOr<ThresholdType> EntityConfigJsonImpl::ParseThresholdType( |
| std::string_view name) { |
| constexpr absl::string_view kUpperCritical = "Upper Critical"; |
| constexpr absl::string_view kUpperNonCritical = "Upper Non Critical"; |
| constexpr absl::string_view kLowerCritical = "Lower Critical"; |
| constexpr absl::string_view kLowerNonCritical = "Lower Non Critical"; |
| if (absl::EqualsIgnoreCase(name, kUpperCritical)) { |
| return THRESHOLD_TYPE_UPPER_CRITICAL; |
| } |
| if (absl::EqualsIgnoreCase(name, kUpperNonCritical)) { |
| return THRESHOLD_TYPE_UPPER_NON_CRITICAL; |
| } |
| if (absl::EqualsIgnoreCase(name, kLowerCritical)) { |
| return THRESHOLD_TYPE_LOWER_CRITICAL; |
| } |
| if (absl::EqualsIgnoreCase(name, kLowerNonCritical)) { |
| return THRESHOLD_TYPE_LOWER_NON_CRITICAL; |
| } |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: Type field is not a valid threshold type: ", name)); |
| } |
| |
| absl::StatusOr<absl::flat_hash_map<std::string, ThresholdConfigs>> |
| EntityConfigJsonImpl::ParseThresholdConfigs(const nlohmann::json& config) { |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| if (!config.contains("Thresholds")) { |
| return label_to_threshold_configs; |
| } |
| const nlohmann::json::array_t* threshold_configs = |
| GetValueAsArray(config, "Thresholds"); |
| if (threshold_configs == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Thresholds field is not an array"); |
| } |
| for (const auto& threshold_element : *threshold_configs) { |
| const std::string* name = GetValueAsString(threshold_element, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| ECCLESIA_ASSIGN_OR_RETURN(ThresholdType threshold_type, |
| ParseThresholdType(*name)); |
| |
| ThresholdConfig threshold_config; |
| threshold_config.set_type(threshold_type); |
| std::optional<double> value = |
| GetValueAsDoubleFromFloatOrInteger(threshold_element, "Value"); |
| if (!value.has_value()) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid threshold config: Value field is not a double: config is ", |
| threshold_element.dump())); |
| } |
| threshold_config.set_value(*value); |
| |
| if (threshold_element.contains("Label")) { |
| const std::string* label = GetValueAsString(threshold_element, "Label"); |
| if (label == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid threshold config: Label field is not a string", |
| threshold_element.dump())); |
| } |
| *label_to_threshold_configs[*label].add_threshold_configs() = |
| threshold_config; |
| } else { |
| *label_to_threshold_configs[""].add_threshold_configs() = |
| threshold_config; |
| } |
| } |
| return label_to_threshold_configs; |
| } |
| |
| absl::StatusOr<absl::flat_hash_map<std::string, ThresholdConfigs>> |
| EntityConfigJsonImpl::ParseThresholdConfigsForHwmonTemp( |
| const nlohmann::json& config, const std::vector<std::string>& labels) { |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| if (!config.contains("Thresholds")) { |
| return label_to_threshold_configs; |
| } |
| const nlohmann::json::array_t* threshold_configs = |
| GetValueAsArray(config, "Thresholds"); |
| if (threshold_configs == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Thresholds field is not an array"); |
| } |
| for (const auto& threshold_element : *threshold_configs) { |
| const std::string* name = GetValueAsString(threshold_element, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| ECCLESIA_ASSIGN_OR_RETURN(ThresholdType threshold_type, |
| ParseThresholdType(*name)); |
| |
| ThresholdConfig threshold_config; |
| threshold_config.set_type(threshold_type); |
| std::optional<double> value = |
| GetValueAsDoubleFromFloatOrInteger(threshold_element, "Value"); |
| if (!value.has_value()) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid threshold config: Value field is not a double: config is ", |
| threshold_element.dump())); |
| } |
| threshold_config.set_value(*value); |
| |
| for (const auto& label : labels) { |
| *label_to_threshold_configs[label].add_threshold_configs() = |
| threshold_config; |
| } |
| } |
| return label_to_threshold_configs; |
| } |
| |
| absl::StatusOr<absl::flat_hash_map<std::string, std::string>> |
| EntityConfigJsonImpl::ParseLabelToName(const nlohmann::json& config, |
| absl::Span<const std::string> labels, |
| bool ignore_zero_index_name) { |
| absl::flat_hash_map<std::string, std::string> label_to_name; |
| for (std::size_t i = 0; i < labels.size(); ++i) { |
| std::string label = labels[i]; |
| std::string key = |
| absl::Substitute("$0_Name", absl::StrReplaceAll(label, {{" ", "_"}})); |
| const std::string* name_str = GetValueAsString(config, key); |
| if (name_str == nullptr && !ignore_zero_index_name) { |
| // Try the other schema if $0_Name is not found. |
| key = (i == 0) ? "Name" : absl::Substitute("Name$0", i); |
| name_str = GetValueAsString(config, key); |
| if (name_str == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: Name field is not a string or missing: ", |
| config.dump())); |
| } |
| } |
| |
| if (name_str == nullptr) { |
| label_to_name[label] = ""; |
| } else { |
| label_to_name[label] = absl::StrReplaceAll(*name_str, {{" ", "_"}}); |
| } |
| } |
| return label_to_name; |
| } |
| |
| // Configure refresh interval |
| template <typename T> |
| absl::Status ParseCommonEntityConfig(const nlohmann::json& config, T& sensor) { |
| const auto* refresh_interval = |
| GetValueAsUint(config, kRefreshIntervalKeyword); |
| if (refresh_interval == nullptr) { |
| return absl::OkStatus(); |
| } |
| auto proto_duration = ecclesia::AbslDurationToProtoDuration( |
| absl::Milliseconds(*refresh_interval)); |
| if (!proto_duration.ok()) { |
| return proto_duration.status(); |
| } |
| *sensor.mutable_entity_common_config()->mutable_refresh_interval() = |
| *proto_duration; |
| |
| if (const auto* queue_size = GetValueAsUint(config, kQueueSizeKeyword); |
| queue_size != nullptr) { |
| sensor.mutable_entity_common_config()->set_queue_size(*queue_size); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<HwmonTempSensorConfig> |
| EntityConfigJsonImpl::ParseHwmonTempSensorConfig(HwmonTempSensorType type, |
| const nlohmann::json& config, |
| bool is_detected) { |
| if (type == HWMON_TEMP_SENSOR_TYPE0_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported HWMon temperature " |
| "sensor type"); |
| } |
| HwmonTempSensorConfig hwmon_temp_sensor_config; |
| hwmon_temp_sensor_config.set_type(type); |
| |
| ECCLESIA_ASSIGN_OR_RETURN( |
| *hwmon_temp_sensor_config.mutable_hal_common_config(), |
| ParseHalCommonConfig(config)); |
| |
| ECCLESIA_RETURN_IF_ERROR( |
| ParseCommonEntityConfig(config, hwmon_temp_sensor_config)); |
| |
| // Default reading range for hwmon temperature sensors. |
| // From: |
| // https://github.com/openbmc/dbus-sensors/blob/af1724b84b7558037c665d2106ec44d7362aca6b/src/hwmon-temp/HwmonTempMain.cpp#L62 |
| constexpr double kDefaultTempMinReading = -128.0; |
| constexpr double kDefaultTempMaxReading = 127.0; |
| ReadingRangeConfig max; |
| max.set_type(READING_RANGE_TYPE_MAX); |
| max.set_reading(kDefaultTempMaxReading); |
| ReadingRangeConfig min; |
| min.set_type(READING_RANGE_TYPE_MIN); |
| min.set_reading(kDefaultTempMinReading); |
| ReadingRangeConfigs reading_range_configs; |
| *reading_range_configs.add_reading_range_configs() = max; |
| *reading_range_configs.add_reading_range_configs() = min; |
| |
| std::vector<std::string> labels; |
| const nlohmann::json::array_t* labels_array = |
| GetValueAsArray(config, "Labels"); |
| |
| // If the labels array is not present, we use the default label "temp$i". |
| if (labels_array == nullptr) { |
| for (size_t i = 0;; i++) { |
| std::string key = "Name"; |
| if (i > 0) key += std::to_string(i); |
| const std::string* name_str = GetValueAsString(config, key); |
| if (name_str == nullptr) { |
| break; |
| } |
| std::string label = absl::Substitute("temp$0", i + 1); |
| (*hwmon_temp_sensor_config.mutable_label_to_name())[label] = *name_str; |
| (*hwmon_temp_sensor_config.mutable_label_to_reading_ranges())[label] = |
| reading_range_configs; |
| labels.push_back(label); |
| } |
| } else { |
| // Otherwise, we use the labels array and the labels will be checked against |
| // the labels files. |
| for (const auto& label : *labels_array) { |
| const std::string* label_str = label.get_ptr<const std::string*>(); |
| if (label_str == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Labels field is not a string array"); |
| } |
| labels.push_back(*label_str); |
| (*hwmon_temp_sensor_config |
| .mutable_label_to_reading_ranges())[*label_str] = |
| reading_range_configs; |
| } |
| absl::flat_hash_map<std::string, std::string> label_to_name; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_name, ParseLabelToName(config, labels)); |
| hwmon_temp_sensor_config.mutable_label_to_name()->insert( |
| label_to_name.begin(), label_to_name.end()); |
| } |
| |
| std::optional<RelatedItem> related_item = ParseRelatedItem(config); |
| if (related_item.has_value()) { |
| *hwmon_temp_sensor_config.mutable_entity_common_config() |
| ->mutable_related_item() = *related_item; |
| } |
| |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs, |
| ParseThresholdConfigsForHwmonTemp(config, labels)); |
| hwmon_temp_sensor_config.mutable_label_to_thresholds()->insert( |
| label_to_threshold_configs.begin(), label_to_threshold_configs.end()); |
| |
| const std::string* sensor_group = |
| GetValueAsString(config, kSensorGroupKeyword); |
| if (sensor_group != nullptr) { |
| hwmon_temp_sensor_config.set_sensor_group(*sensor_group); |
| } |
| |
| hwmon_temp_sensor_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| return hwmon_temp_sensor_config; |
| } |
| |
| absl::StatusOr<FanControllerConfig> |
| EntityConfigJsonImpl::ParseFanControllerConfig(FanControllerType type, |
| const nlohmann::json& config, |
| bool is_detected) { |
| if (type == FAN_CONTROLLER_TYPE_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported Fan controller type"); |
| } |
| FanControllerConfig fan_controller_config; |
| fan_controller_config.set_type(type); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(*fan_controller_config.mutable_hal_common_config(), |
| ParseHalCommonConfig(config)); |
| |
| fan_controller_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| return fan_controller_config; |
| } |
| |
| absl::StatusOr<PsuSensorConfig> EntityConfigJsonImpl::ParsePsuSensorConfig( |
| PsuSensorType type, const nlohmann::json& config, bool is_detected) { |
| if (type == PSU_SENSOR_TYPE0_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported PSU sensor type"); |
| } |
| PsuSensorConfig psu_sensor_config; |
| psu_sensor_config.set_type(type); |
| |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| psu_sensor_config.set_name(*name); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(*psu_sensor_config.mutable_hal_common_config(), |
| ParseHalCommonConfig(config)); |
| ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, psu_sensor_config)); |
| |
| std::vector<std::string> labels; |
| ECCLESIA_ASSIGN_OR_RETURN(labels, ParseLabels(config)); |
| |
| absl::flat_hash_map<std::string, std::string> label_to_name; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_name, |
| ParseLabelToName(config, labels, true)); |
| psu_sensor_config.mutable_label_to_name()->insert(label_to_name.begin(), |
| label_to_name.end()); |
| |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs, |
| ParseThresholdConfigs(config)); |
| psu_sensor_config.mutable_label_to_thresholds()->insert( |
| label_to_threshold_configs.begin(), label_to_threshold_configs.end()); |
| |
| absl::flat_hash_map<std::string, ReadingRangeConfigs> label_to_reading_range = |
| ParseReadingRangeConfigs(config, labels); |
| psu_sensor_config.mutable_label_to_reading_ranges()->insert( |
| label_to_reading_range.begin(), label_to_reading_range.end()); |
| |
| absl::flat_hash_map<std::string, ReadingTransformConfig> |
| label_to_reading_transform = ParseReadingTransformConfigs(config, labels); |
| psu_sensor_config.mutable_label_to_reading_transform()->insert( |
| label_to_reading_transform.begin(), label_to_reading_transform.end()); |
| |
| std::optional<RelatedItem> related_item = ParseRelatedItem(config); |
| if (related_item.has_value()) { |
| *psu_sensor_config.mutable_entity_common_config()->mutable_related_item() = |
| *related_item; |
| } |
| |
| const std::string* sensor_group = |
| GetValueAsString(config, kSensorGroupKeyword); |
| if (sensor_group != nullptr) { |
| psu_sensor_config.set_sensor_group(*sensor_group); |
| } |
| |
| psu_sensor_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| return psu_sensor_config; |
| } |
| |
| absl::StatusOr<FanPwmConfig> EntityConfigJsonImpl::ParseFanPwmConfig( |
| FanPwmType type, const nlohmann::json& config, bool is_detected) { |
| if (type == PWM_SENSOR_TYPE_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported Fan PWM type"); |
| } |
| FanPwmConfig fan_pwm_config; |
| fan_pwm_config.set_type(type); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(*fan_pwm_config.mutable_hal_common_config(), |
| ParseHalCommonConfig(config)); |
| |
| // Fan PWM has fixed max and min reading ranges. |
| ReadingRangeConfig max; |
| max.set_type(READING_RANGE_TYPE_MAX); |
| max.set_reading(100); |
| *fan_pwm_config.mutable_reading_ranges()->add_reading_range_configs() = max; |
| ReadingRangeConfig min; |
| min.set_type(READING_RANGE_TYPE_MIN); |
| min.set_reading(0); |
| *fan_pwm_config.mutable_reading_ranges()->add_reading_range_configs() = min; |
| |
| const nlohmann::json* connector = GetValueAsJson(config, "Connector"); |
| if (connector == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Connector field is not a JSON"); |
| } |
| const std::string* name = GetValueAsString(*connector, "PwmName"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Connector PwmName field is not a string"); |
| } |
| fan_pwm_config.set_name(*name); |
| const uint64_t* index = GetValueAsUint(*connector, "Pwm"); |
| if (index == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Connector Index field is not a uint64_t"); |
| } |
| fan_pwm_config.set_index(static_cast<int32_t>(*index + 1)); |
| |
| ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, fan_pwm_config)); |
| |
| std::optional<RelatedItem> related_item = ParseRelatedItem(config); |
| if (related_item.has_value()) { |
| *fan_pwm_config.mutable_entity_common_config()->mutable_related_item() = |
| *related_item; |
| } |
| |
| const std::string* sensor_group = |
| GetValueAsString(config, kSensorGroupKeyword); |
| if (sensor_group != nullptr) { |
| fan_pwm_config.set_sensor_group(*sensor_group); |
| } |
| |
| fan_pwm_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| return fan_pwm_config; |
| } |
| |
| absl::StatusOr<FanTachConfig> EntityConfigJsonImpl::ParseFanTachConfig( |
| FanTachType type, const nlohmann::json& config, bool is_detected) { |
| if (type == TACH_SENSOR_TYPE_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported Fan PWM type"); |
| } |
| FanTachConfig fan_tach_config; |
| fan_tach_config.set_type(type); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(*fan_tach_config.mutable_hal_common_config(), |
| ParseHalCommonConfig(config)); |
| |
| constexpr int kDefaultMaxFanReading = 25000; |
| constexpr int kDefaultMinFanReading = 0; |
| ReadingRangeConfigs reading_range_configs = ParseReadingRangeConfigs( |
| config, kDefaultMinFanReading, kDefaultMaxFanReading); |
| *fan_tach_config.mutable_reading_ranges() = reading_range_configs; |
| |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| fan_tach_config.set_name(*name); |
| const uint64_t* index = GetValueAsUint(config, "Index"); |
| if (index == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Index field is not a uint64_t"); |
| } |
| fan_tach_config.set_index(static_cast<int32_t>(*index + 1)); |
| |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs, |
| ParseThresholdConfigs(config)); |
| *fan_tach_config.mutable_thresholds() = label_to_threshold_configs[""]; |
| |
| ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, fan_tach_config)); |
| |
| std::optional<RelatedItem> related_item = ParseRelatedItem(config); |
| if (related_item.has_value()) { |
| *fan_tach_config.mutable_entity_common_config()->mutable_related_item() = |
| *related_item; |
| } |
| |
| const std::string* sensor_group = |
| GetValueAsString(config, kSensorGroupKeyword); |
| if (sensor_group != nullptr) { |
| fan_tach_config.set_sensor_group(*sensor_group); |
| } |
| |
| fan_tach_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| return fan_tach_config; |
| } |
| |
| absl::StatusOr<SharedMemSensorConfig> |
| EntityConfigJsonImpl::ParseSharedMemSensorConfig(SharedMemSensorType type, |
| const nlohmann::json& config, |
| bool is_detected) { |
| if (type == SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported Shared Mem Sensor type"); |
| } |
| SharedMemSensorConfig shared_mem_sensor_config; |
| shared_mem_sensor_config.set_type(type); |
| |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| shared_mem_sensor_config.set_name(*name); |
| |
| shared_mem_sensor_config.set_unit(ParseSensorUnit(config)); |
| |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs, |
| ParseThresholdConfigs(config)); |
| *shared_mem_sensor_config.mutable_thresholds() = |
| label_to_threshold_configs[""]; |
| |
| ECCLESIA_RETURN_IF_ERROR( |
| ParseCommonEntityConfig(config, shared_mem_sensor_config)); |
| |
| shared_mem_sensor_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| return shared_mem_sensor_config; |
| } |
| |
| absl::StatusOr<IntelCpuSensorConfig> |
| EntityConfigJsonImpl::ParseIntelCpuSensorConfig(IntelCpuSensorType type, |
| const nlohmann::json& config, |
| bool is_detected) { |
| if (type == INTEL_CPU_SENSOR_TYPE0_UNKNOWN) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Type field is not a supported Intel CPU sensor type"); |
| } |
| IntelCpuSensorConfig intel_cpu_sensor_config; |
| intel_cpu_sensor_config.set_type(type); |
| |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| intel_cpu_sensor_config.set_name(*name); |
| |
| ECCLESIA_ASSIGN_OR_RETURN( |
| *intel_cpu_sensor_config.mutable_hal_common_config(), |
| ParseHalCommonConfig(config)); |
| ECCLESIA_RETURN_IF_ERROR( |
| ParseCommonEntityConfig(config, intel_cpu_sensor_config)); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(std::vector<std::string> labels, |
| ParseLabels(config)); |
| |
| absl::flat_hash_map<std::string, std::string> label_to_name; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_name, |
| ParseLabelToName(config, labels, true)); |
| intel_cpu_sensor_config.mutable_label_to_name()->insert(label_to_name.begin(), |
| label_to_name.end()); |
| |
| intel_cpu_sensor_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(uint64_t cpu_id, ParseCpuId(config)); |
| intel_cpu_sensor_config.set_cpu_id(cpu_id); |
| |
| // From |
| // https://github.com/openbmc/dbus-sensors/blob/master/src/intel-cpu/IntelCPUSensorMain.cpp#L371 |
| if (std::optional<double> dts_offset = |
| GetValueAsDoubleFromFloatOrInteger(config, kDtsOffsetKeyword); |
| dts_offset.has_value()) { |
| intel_cpu_sensor_config.set_dts_offset(*dts_offset); |
| } |
| |
| const std::string* sensor_group = |
| GetValueAsString(config, kSensorGroupKeyword); |
| if (sensor_group != nullptr) { |
| intel_cpu_sensor_config.set_sensor_group(*sensor_group); |
| } |
| |
| return intel_cpu_sensor_config; |
| } |
| |
| absl::StatusOr<VirtualSensorConfig> |
| EntityConfigJsonImpl::ParseVirtualSensorConfig(const nlohmann::json& config, |
| bool is_detected) { |
| VirtualSensorConfig virtual_sensor_config; |
| const std::string* expression = GetValueAsString(config, "Expression"); |
| if (expression == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Expression field is not a string or missing"); |
| } |
| virtual_sensor_config.set_expression(*expression); |
| virtual_sensor_config.set_unit(ParseSensorUnit(config)); |
| virtual_sensor_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| |
| absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs; |
| ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs, |
| ParseThresholdConfigs(config)); |
| *virtual_sensor_config.mutable_thresholds() = label_to_threshold_configs[""]; |
| |
| constexpr int kDefaultMaxVirtualSensorReading = 100; |
| constexpr int kDefaultMinVirtualSensorReading = 0; |
| ReadingRangeConfigs reading_range_configs = ParseReadingRangeConfigs( |
| config, kDefaultMinVirtualSensorReading, kDefaultMaxVirtualSensorReading); |
| *virtual_sensor_config.mutable_reading_ranges() = reading_range_configs; |
| |
| ECCLESIA_RETURN_IF_ERROR( |
| ParseCommonEntityConfig(config, virtual_sensor_config)); |
| |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not a string or missing"); |
| } |
| virtual_sensor_config.set_name(*name); |
| |
| return virtual_sensor_config; |
| } |
| |
| absl::Status EntityConfigJsonImpl::ParseFanConfig( |
| EntityConfigJsonImplMutableData& mutable_data, const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node, |
| std::string_view top_level_config_name) { |
| const bool* tlbmc_owned = GetValueAsBool(config, "TlbmcOwned"); |
| // If not tlbmc owned then ignore |
| if (tlbmc_owned == nullptr || !*tlbmc_owned) { |
| return absl::OkStatus(); |
| } |
| const std::string* type_str = GetValueAsString(config, "Type"); |
| if (type_str == nullptr || *type_str != kFanKeyword) { |
| return absl::OkStatus(); |
| } |
| |
| const std::string* name = GetValueAsString(config, "Name"); |
| if (name == nullptr) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: Name field is not found for fan config: ", |
| config.dump())); |
| } |
| |
| // LocationType and ServiceLabel are used for topology generation, so is |
| // required |
| const std::string* location_type = GetValueAsString(config, "LocationType"); |
| if (location_type == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: LocationType field is not found for fan config: ", |
| config.dump())); |
| } |
| |
| const std::string* service_label = GetValueAsString(config, "ServiceLabel"); |
| if (service_label == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: ServiceLabel field is not found for fan config: ", |
| config.dump())); |
| } |
| |
| const bool* is_hotpluggable = GetValueAsBool(config, "HotPluggable"); |
| if (is_hotpluggable == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: HotPluggable field is not found for fan config: ", |
| config.dump())); |
| } |
| |
| const std::string* pwm_sensor_name = GetValueAsString(config, "PWMSensor"); |
| if (pwm_sensor_name == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: PWMSensor field is not found for fan config: ", |
| config.dump())); |
| } |
| |
| const std::string* tach_sensor_name = GetValueAsString(config, "TachSensor"); |
| if (tach_sensor_name == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: TachSensor field is not found for fan config: ", |
| config.dump())); |
| } |
| |
| // Each fan should have a unique node name |
| std::string fan_node_name = |
| absl::StrFormat("%s_%s", top_level_config_name, *name); |
| |
| // Add fan to fru table. |
| Fru fan_fru; |
| fan_fru.mutable_data()->mutable_fan_info()->set_name(*name); |
| const std::string* model = GetValueAsString(config, "Model"); |
| if (model != nullptr) { |
| fan_fru.mutable_data()->mutable_fan_info()->set_model(*model); |
| } |
| fan_fru.mutable_data()->mutable_fan_info()->set_is_hotpluggable( |
| *is_hotpluggable); |
| fan_fru.mutable_data()->mutable_fan_info()->set_pwm_sensor_name( |
| *pwm_sensor_name); |
| fan_fru.mutable_data()->mutable_fan_info()->set_tach_sensor_name( |
| *tach_sensor_name); |
| |
| fan_fru.mutable_attributes()->set_resource_type(RESOURCE_TYPE_FAN); |
| fan_fru.mutable_attributes()->set_key(fan_node_name); |
| fan_fru.mutable_attributes()->set_status(Status::STATUS_READY); |
| fan_fru.mutable_attributes()->mutable_refresh_policy()->set_refresh_mode( |
| RefreshMode::REFRESH_MODE_ON_DEMAND); |
| |
| // Fan should be its own Topology Node as it has its own machine level devpath |
| TopologyConfigNode fan_node; |
| fan_node.set_name(*name); |
| fan_node.mutable_fru_info()->set_fru_key(fan_node_name); |
| fan_node.set_config_key(fan_node_name); |
| fan_node.mutable_location_context()->set_location_type( |
| GetPartLocationTypeFromRedfishString(*location_type)); |
| |
| // Add topology port configs for upstream and downstream |
| PortConfig chassis_to_fan_port_config; |
| chassis_to_fan_port_config.set_port_type(PORT_TYPE_DOWNSTREAM); |
| chassis_to_fan_port_config.set_port_name(fan_node_name); |
| chassis_to_fan_port_config.set_port_label(*service_label); |
| topology_config_node.mutable_port_configs()->insert( |
| {fan_node_name, std::move(chassis_to_fan_port_config)}); |
| |
| PortConfig fan_to_chassis_port_config; |
| fan_to_chassis_port_config.set_port_type(PORT_TYPE_UPSTREAM); |
| fan_to_chassis_port_config.set_port_name(fan_node_name); |
| *fan_node.add_upstream_port_configs() = std::move(fan_to_chassis_port_config); |
| |
| // The config name and the fru key are identical. |
| mutable_data.topology_config.mutable_topology_config_nodes()->insert( |
| {fan_node_name, std::move(fan_node)}); |
| mutable_data.topology_config.mutable_fru_configs()->insert( |
| {fan_node_name, fan_node_name}); |
| mutable_data.fru_table.mutable_key_to_fru()->insert( |
| {fan_node_name, std::move(fan_fru)}); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<NicTelemetryConfig> |
| EntityConfigJsonImpl::ParseNicTelemetryConfig(const nlohmann::json& config, |
| bool is_detected) { |
| NicTelemetryConfig nic_telemetry_config; |
| std::optional<uint64_t> bus = |
| GetValueAsUintFromStringOrInteger(config, "Bus"); |
| if (!bus.has_value()) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: element does not have a Bus field: ", config.dump())); |
| } |
| nic_telemetry_config.mutable_hal_common_config()->set_bus(*bus); |
| |
| const std::string* version = GetValueAsString(config, "Version"); |
| if (version == nullptr) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid config: element does not have a Version field: ", |
| config.dump())); |
| } |
| if (*version == "v1" || *version == "V1") { |
| nic_telemetry_config.set_version(nic_veeprom::NIC_TELEMETRY_VERSION_V1); |
| } else if (*version == "v2" || *version == "V2") { |
| nic_telemetry_config.set_version(nic_veeprom::NIC_TELEMETRY_VERSION_V2); |
| } else { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: element does not have a supported Version field: ", |
| config.dump())); |
| } |
| std::vector<nic_veeprom::NicTelemetryName> telemetry_names; |
| ECCLESIA_ASSIGN_OR_RETURN(telemetry_names, ParseNicTelemetryNames(config)); |
| for (const auto& telemetry_name : telemetry_names) { |
| nic_telemetry_config.add_telemetry_names(telemetry_name); |
| } |
| |
| nic_telemetry_config.mutable_entity_common_config()->set_config_detected( |
| is_detected); |
| return nic_telemetry_config; |
| } |
| |
| absl::Status EntityConfigJsonImpl::ParseProcessorConfig( |
| EntityConfigJsonImplMutableData& mutable_data, const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node, |
| std::string_view top_level_config_name) { |
| const std::string* type_str = GetValueAsString(config, "Type"); |
| const bool* tlbmc_owned = GetValueAsBool(config, "TlbmcOwned"); |
| IntelCpuSensorType intel_cpu_sensor_type = INTEL_CPU_SENSOR_TYPE0_UNKNOWN; |
| if (tlbmc_owned != nullptr && *tlbmc_owned) { |
| intel_cpu_sensor_type = IsIntelCpuSensor(*type_str); |
| } |
| if (type_str == nullptr || |
| (intel_cpu_sensor_type == INTEL_CPU_SENSOR_TYPE0_UNKNOWN && |
| *type_str != kProcessorKeyword)) { |
| return absl::OkStatus(); |
| } |
| |
| const std::string* processor_id = GetValueAsString(config, "Name"); |
| if (processor_id == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not found for processor config"); |
| } |
| |
| // Each processor should have a unique node name |
| std::string processor_node_name = |
| absl::StrFormat("%s_%s", top_level_config_name, *processor_id); |
| |
| // Add processor to fru table. |
| Fru processor_fru; |
| processor_fru.mutable_attributes()->set_resource_type( |
| RESOURCE_TYPE_PROCESSOR); |
| processor_fru.mutable_attributes()->set_key(processor_node_name); |
| processor_fru.mutable_attributes()->set_status(Status::STATUS_READY); |
| |
| // Processor should be its own Topology Node as it has its own machine level |
| // devpath |
| TopologyConfigNode processor_node; |
| processor_node.set_name(*processor_id); |
| processor_node.mutable_fru_info()->set_fru_key(processor_node_name); |
| processor_node.set_config_key(processor_node_name); |
| |
| // Add topology port configs for upstream and downstream |
| PortConfig chassis_to_processor_port_config; |
| chassis_to_processor_port_config.set_port_type(PORT_TYPE_DOWNSTREAM); |
| chassis_to_processor_port_config.set_port_name(processor_node_name); |
| chassis_to_processor_port_config.set_port_label( |
| absl::AsciiStrToUpper(*processor_id)); |
| topology_config_node.mutable_port_configs()->insert( |
| {processor_node_name, std::move(chassis_to_processor_port_config)}); |
| |
| PortConfig processor_to_chassis_port_config; |
| processor_to_chassis_port_config.set_port_type(PORT_TYPE_UPSTREAM); |
| processor_to_chassis_port_config.set_port_name(processor_node_name); |
| *processor_node.add_upstream_port_configs() = |
| std::move(processor_to_chassis_port_config); |
| |
| // The config name and the fru key are identical. |
| mutable_data.topology_config.mutable_topology_config_nodes()->insert( |
| {processor_node_name, std::move(processor_node)}); |
| mutable_data.topology_config.mutable_fru_configs()->insert( |
| {processor_node_name, processor_node_name}); |
| mutable_data.fru_table.mutable_key_to_fru()->insert( |
| {processor_node_name, std::move(processor_fru)}); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status EntityConfigJsonImpl::ParseDimmConfig( |
| EntityConfigJsonImplMutableData& mutable_data, const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node, |
| std::string_view top_level_config_name) { |
| const bool* tlbmc_owned = GetValueAsBool(config, "TlbmcOwned"); |
| // If not tlbmc owned then ignore |
| if (tlbmc_owned == nullptr || !*tlbmc_owned) { |
| return absl::OkStatus(); |
| } |
| const std::string* type_str = GetValueAsString(config, "Type"); |
| if (type_str == nullptr || *type_str != kDimmKeyword) { |
| return absl::OkStatus(); |
| } |
| |
| const std::string* dimm_name = GetValueAsString(config, "Name"); |
| if (dimm_name == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Name field is not found for dimm config"); |
| } |
| |
| const std::string* service_label = GetValueAsString(config, "ServiceLabel"); |
| if (service_label == nullptr) { |
| return absl::InvalidArgumentError( |
| "Invalid config: ServiceLabel field is not found for dimm config"); |
| } |
| |
| // Each dimm should have a unique node name |
| std::string dimm_node_name = |
| absl::StrFormat("%s_%s", top_level_config_name, *dimm_name); |
| |
| // Add dimm to fru table. |
| Fru dimm_fru; |
| dimm_fru.mutable_attributes()->set_resource_type(RESOURCE_TYPE_DIMM); |
| dimm_fru.mutable_attributes()->set_key(dimm_node_name); |
| dimm_fru.mutable_attributes()->set_status(Status::STATUS_READY); |
| |
| // Dimm should be its own Topology Node as it has its own machine level |
| // devpath |
| TopologyConfigNode dimm_node; |
| dimm_node.set_name(*dimm_name); |
| dimm_node.mutable_fru_info()->set_fru_key(dimm_node_name); |
| dimm_node.set_config_key(dimm_node_name); |
| |
| // Add topology port configs for upstream and downstream |
| PortConfig chassis_to_dimm_port_config; |
| chassis_to_dimm_port_config.set_port_type(PORT_TYPE_DOWNSTREAM); |
| chassis_to_dimm_port_config.set_port_name(dimm_node_name); |
| chassis_to_dimm_port_config.set_port_label(*service_label); |
| topology_config_node.mutable_port_configs()->insert( |
| {dimm_node_name, std::move(chassis_to_dimm_port_config)}); |
| |
| PortConfig dimm_to_chassis_port_config; |
| dimm_to_chassis_port_config.set_port_type(PORT_TYPE_UPSTREAM); |
| dimm_to_chassis_port_config.set_port_name(dimm_node_name); |
| *dimm_node.add_upstream_port_configs() = |
| std::move(dimm_to_chassis_port_config); |
| |
| // The config name and the fru key are identical. |
| mutable_data.topology_config.mutable_topology_config_nodes()->insert( |
| {dimm_node_name, std::move(dimm_node)}); |
| mutable_data.topology_config.mutable_fru_configs()->insert( |
| {dimm_node_name, dimm_node_name}); |
| mutable_data.fru_table.mutable_key_to_fru()->insert( |
| {dimm_node_name, std::move(dimm_fru)}); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status EntityConfigJsonImpl::LinkParentChildNodes( |
| Fru& current_fru, TopologyConfigNode& current_node, Fru& downstream_fru, |
| TopologyConfigNode& downstream_node) { |
| // Link current node -> downstream node. |
| if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) { |
| current_node.add_children_chassis_ids(downstream_node.name()); |
| } else if (downstream_fru.attributes().resource_type() == |
| RESOURCE_TYPE_ASSEMBLY) { |
| current_node.add_children_assembly_ids(downstream_node.name()); |
| } else if (downstream_fru.attributes().resource_type() == |
| RESOURCE_TYPE_CABLE) { |
| if (current_fru.attributes().resource_type() == RESOURCE_TYPE_CABLE) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: Cable cannot be connected to another cable.", |
| current_node.name(), " -> ", downstream_node.name())); |
| } |
| current_node.add_children_cable_ids(downstream_node.name()); |
| } else if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_FAN) { |
| current_node.mutable_children_fans()->insert( |
| {downstream_node.name(), downstream_node.config_key()}); |
| } else if (downstream_fru.attributes().resource_type() == |
| RESOURCE_TYPE_PROCESSOR) { |
| current_node.mutable_children_processors()->insert( |
| {downstream_node.name(), downstream_node.config_key()}); |
| } else if (downstream_fru.attributes().resource_type() == |
| RESOURCE_TYPE_DIMM) { |
| current_node.mutable_children_dimms()->insert( |
| {downstream_node.name(), downstream_node.config_key()}); |
| } |
| |
| // Link current node <- downstream node. |
| if (current_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) { |
| // Only cable can have multiple upstream boards for now. |
| if (downstream_fru.attributes().resource_type() != RESOURCE_TYPE_CABLE && |
| !downstream_node.parent_chassis_ids().empty()) { |
| return absl::InternalError( |
| absl::StrCat("Invalid config: ", downstream_node.name(), |
| " has more than one parent chassis, which is not " |
| "expected.")); |
| } |
| downstream_node.set_parent_resource_id(current_node.name()); |
| downstream_node.add_parent_chassis_ids(current_node.name()); |
| } else if (current_fru.attributes().resource_type() == RESOURCE_TYPE_CABLE) { |
| downstream_node.add_parent_cable_ids(current_node.name()); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status EntityConfigJsonImpl::ValidateTopology( |
| const absl::flat_hash_set<absl::string_view>& expected_nodes_traversed, |
| const absl::flat_hash_set<absl::string_view>& nodes_traversed) { |
| if (expected_nodes_traversed != nodes_traversed) { |
| // Since the BFS through the topology will validate every fru key, if we |
| // traverse through more nodes than expected, that means there are multiple |
| // configs probing the same fru key. |
| if (GetTlbmcConfig() |
| .fru_collector_module() |
| .multiple_configs_probe_raw_fru() && |
| nodes_traversed.size() > expected_nodes_traversed.size()) { |
| return absl::OkStatus(); |
| } |
| |
| std::vector<absl::string_view> missing_nodes; |
| for (const auto& node : expected_nodes_traversed) { |
| if (nodes_traversed.find(node) == nodes_traversed.end()) { |
| missing_nodes.push_back(node); |
| } |
| } |
| std::vector<absl::string_view> unexpected_nodes; |
| for (const auto& node : nodes_traversed) { |
| if (expected_nodes_traversed.find(node) == |
| expected_nodes_traversed.end()) { |
| unexpected_nodes.push_back(node); |
| } |
| } |
| return absl::InternalError(absl::Substitute( |
| "The following FRUs are not connected to the topology: $0, which " |
| "means the config in this machine is not fully tested by tlBMC. The " |
| "following FRUs were found in the topology, but not expected: $1", |
| absl::StrJoin(missing_nodes, ", "), |
| absl::StrJoin(unexpected_nodes, ", "))); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status EntityConfigJsonImpl::CreateAssociationsBetweenTopologyConfigNodes( |
| TopologyConfig& topology_config, FruTable& fru_table, |
| absl::flat_hash_set<absl::string_view>& expected_nodes_traversed) { |
| if (topology_config.topology_config_nodes().empty()) { |
| return absl::InternalError( |
| "Internal error: Somehow topology config is empty. You may need to add " |
| "ports to the EM Configs."); |
| } |
| |
| // Find root node in TopologyConfig. |
| std::string root_chassis_location_code; |
| for (const auto& [config_key, topology_config_node] : |
| topology_config.topology_config_nodes()) { |
| // There are multiple inconsistent root chassis location codes found in the |
| // configs. Stop the topology buildup with an error. |
| if (!root_chassis_location_code.empty() && |
| topology_config_node.has_root_chassis_location_code()) { |
| return absl::InternalError( |
| absl::StrFormat("Invalid config: Multiple root chassis location " |
| "codes found! Previous: %s. Current: %s", |
| root_chassis_location_code, |
| topology_config_node.root_chassis_location_code())); |
| } |
| if (topology_config_node.has_root_chassis_location_code()) { |
| root_chassis_location_code = |
| topology_config_node.root_chassis_location_code(); |
| } |
| if (topology_config_node.upstream_port_configs().empty()) { |
| if (topology_config.has_root_node_key()) { |
| return absl::InternalError("Invalid config: Multiple root nodes found"); |
| } |
| topology_config.set_root_node_key(config_key); |
| } |
| } |
| |
| if (!topology_config.has_root_node_key()) { |
| return absl::InternalError("Invalid config: No root node found"); |
| } |
| |
| // Map of port name to config key. |
| absl::flat_hash_map<std::string, std::string> |
| upstream_port_name_to_config_key; |
| for (const auto& [config_key, topology_config_node] : |
| topology_config.topology_config_nodes()) { |
| for (const auto& upstream_port_config : |
| topology_config_node.upstream_port_configs()) { |
| if (!upstream_port_config.port_name().empty()) { |
| upstream_port_name_to_config_key[upstream_port_config.port_name()] = |
| config_key; |
| } |
| } |
| } |
| |
| // Derive devpath for each topology config node. |
| // Not checking for presence of root node since it is already checked above. |
| TopologyConfigNode& root_node = |
| topology_config.mutable_topology_config_nodes()->at( |
| topology_config.root_node_key()); |
| root_node.mutable_location_context()->set_devpath(absl::StrCat( |
| "/", (root_chassis_location_code.empty() |
| ? "phys" |
| : root_node.root_chassis_location_code().c_str()))); |
| LOG(INFO) << "Final root devpath: " << root_node.location_context().devpath(); |
| |
| // Compare with the node names to ensure the devpath is the alphabetically |
| // smallest. |
| std::priority_queue<TopologyConfigNode*, std::vector<TopologyConfigNode*>, |
| TopologyConfigNodePtrComparator> |
| node_queue; |
| node_queue.push(&root_node); |
| absl::flat_hash_set<absl::string_view> nodes_traversed; |
| while (!node_queue.empty()) { |
| TopologyConfigNode* current_node = node_queue.top(); |
| node_queue.pop(); |
| |
| // Always populate root chassis location code for all nodes. |
| current_node->set_root_chassis_location_code(root_chassis_location_code); |
| |
| nodes_traversed.insert(current_node->config_key()); |
| auto find_current_fru = fru_table.mutable_key_to_fru()->find( |
| current_node->fru_info().fru_key()); |
| if (find_current_fru == fru_table.key_to_fru().end()) { |
| LOG(WARNING) << "FRU not found for key while setting devpath: " |
| << current_node->fru_info().fru_key() |
| << ". This is normally not expected. Check debug endpoint " |
| "and reproduce the issue."; |
| continue; |
| } |
| Fru& current_fru = find_current_fru->second; |
| for (const auto& [port_name, port_config] : current_node->port_configs()) { |
| auto find_downstream_node_key_it = |
| upstream_port_name_to_config_key.find(port_name); |
| if (find_downstream_node_key_it == |
| upstream_port_name_to_config_key.end()) { |
| continue; |
| } |
| absl::string_view downstream_node_key = |
| find_downstream_node_key_it->second; |
| // Downstream node is always present in the topology config so will not |
| // check for presence. |
| TopologyConfigNode& downstream_node = |
| topology_config.mutable_topology_config_nodes()->at( |
| downstream_node_key); |
| // Downstream node key is always present in the FRU table so will not |
| // check for presence. |
| Fru& downstream_fru = fru_table.mutable_key_to_fru()->at( |
| downstream_node.fru_info().fru_key()); |
| |
| absl::Status status = LinkParentChildNodes( |
| current_fru, *current_node, downstream_fru, downstream_node); |
| if (!status.ok()) { |
| return status; |
| } |
| |
| // If the downstream node has a devpath, then we can skip the devpath |
| // generation logic below and does not need to push it onto the queue |
| // again. |
| if (downstream_node.has_location_context() && |
| downstream_node.location_context().has_devpath()) { |
| continue; |
| } |
| // If node is Sub-Fru then we add :device to the devpath and then append |
| // the port label. |
| if (port_config.port_label() != kDontSetDevpathKeyword) { |
| if (downstream_node.fru_info().is_sub_fru() || |
| downstream_node.location_context().location_type() == |
| PART_LOCATION_TYPE_EMBEDDED) { |
| downstream_node.mutable_location_context()->set_devpath( |
| absl::StrCat(current_node->mutable_location_context()->devpath(), |
| ":device:", port_config.port_label())); |
| } else { |
| downstream_node.mutable_location_context()->set_devpath( |
| absl::StrCat(current_node->mutable_location_context()->devpath(), |
| "/", port_config.port_label())); |
| } |
| } |
| node_queue.push(&downstream_node); |
| } |
| } |
| return ValidateTopology(expected_nodes_traversed, nodes_traversed); |
| } |
| |
| absl::StatusOr<const TopologyConfigNode*> EntityConfigJsonImpl::GetFruTopology( |
| absl::string_view fru_key) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| const auto fru_config_it = topology_config->fru_configs().find(fru_key); |
| if (fru_config_it == topology_config->fru_configs().end()) { |
| return absl::NotFoundError(absl::StrCat("FRU key not found: ", fru_key)); |
| } |
| return &(topology_config->topology_config_nodes().at(fru_config_it->second)); |
| } |
| |
| absl::StatusOr<const TopologyConfigNode*> |
| EntityConfigJsonImpl::GetFruTopologyByConfig( |
| absl::string_view config_key) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| const auto fru_config_it = |
| topology_config->topology_config_nodes().find(config_key); |
| if (fru_config_it == topology_config->topology_config_nodes().end()) { |
| return absl::NotFoundError( |
| absl::StrCat("config key not found: ", config_key)); |
| } |
| return &(fru_config_it->second); |
| } |
| |
| absl::StatusOr<const TopologyConfig*> EntityConfigJsonImpl::GetTopologyConfig() |
| const { |
| ECCLESIA_RETURN_IF_ERROR(GetParsedStatus()); |
| |
| return &data_store_.mutable_data.Get()->topology_config; |
| } |
| |
| absl::StatusOr<std::vector<std::string>> |
| EntityConfigJsonImpl::GetAllConfigKeys() const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| std::vector<std::string> config_keys; |
| for (const auto& [config_key, topology_config_node] : |
| topology_config->topology_config_nodes()) { |
| if (!topology_config_node.not_owned_by_tlbmc()) { |
| config_keys.push_back(config_key); |
| } |
| } |
| std::sort(config_keys.begin(), config_keys.end()); |
| return config_keys; |
| } |
| |
| absl::StatusOr<std::string> EntityConfigJsonImpl::GetConfigKeyByFruKey( |
| absl::string_view fru_key) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| auto fru_config_it = topology_config->fru_configs().find(fru_key); |
| if (fru_config_it == topology_config->fru_configs().end()) { |
| return absl::NotFoundError(absl::StrCat("FRU key not found: ", fru_key)); |
| } |
| return fru_config_it->second; |
| } |
| |
| absl::StatusOr<std::string> EntityConfigJsonImpl::GetFruKeyByConfigKey( |
| absl::string_view config_key) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| auto fru_config_it = |
| topology_config->topology_config_nodes().find(config_key); |
| if (fru_config_it == topology_config->topology_config_nodes().end()) { |
| return absl::NotFoundError( |
| absl::StrCat("config key not found: ", config_key)); |
| } |
| return fru_config_it->second.fru_info().fru_key(); |
| } |
| |
| absl::StatusOr<const Fru*> EntityConfigJsonImpl::GetFru( |
| absl::string_view key) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const FruTable* fru_table, GetAllFrus()); |
| auto it = fru_table->key_to_fru().find(key); |
| if (it == fru_table->key_to_fru().end()) { |
| return absl::NotFoundError(absl::StrCat("FRU key not found: ", key)); |
| } |
| return &it->second; |
| } |
| |
| absl::StatusOr<const FruTable*> EntityConfigJsonImpl::GetAllFrus() const { |
| ECCLESIA_RETURN_IF_ERROR(GetParsedStatus()); |
| |
| return &data_store_.mutable_data.Get()->fru_table; |
| } |
| |
| absl::StatusOr<std::string> EntityConfigJsonImpl::GetFruDevpath( |
| absl::string_view fru_key) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| auto fru_config_it = topology_config->fru_configs().find(fru_key); |
| if (fru_config_it == topology_config->fru_configs().end()) { |
| return absl::NotFoundError(absl::StrCat("FRU key not found: ", fru_key)); |
| } |
| return topology_config->topology_config_nodes() |
| .at(fru_config_it->second) |
| .location_context() |
| .devpath(); |
| } |
| |
| nlohmann::json EntityConfigJsonImpl::ToJson() const { |
| nlohmann::json json; |
| json["HwmonTempSensorConfigs"] = nlohmann::json::array(); |
| for (const auto& hwmon_temp_sensor_config : |
| data_store_.immutable_data.hwmon_temp_sensor_configs) { |
| json["HwmonTempSensorConfigs"].push_back( |
| ProtoToJson(hwmon_temp_sensor_config)); |
| } |
| |
| json["PsuSensorConfigs"] = nlohmann::json::array(); |
| for (const auto& psu_sensor_config : |
| data_store_.immutable_data.psu_sensor_configs) { |
| json["PsuSensorConfigs"].push_back(ProtoToJson(psu_sensor_config)); |
| } |
| |
| json["FanControllerConfigs"] = nlohmann::json::array(); |
| for (const auto& fan_controller_config : |
| data_store_.immutable_data.fan_controller_configs) { |
| json["FanControllerConfigs"].push_back(ProtoToJson(fan_controller_config)); |
| } |
| |
| json["FanPwmConfigs"] = nlohmann::json::array(); |
| for (const auto& fan_pwm_config : |
| data_store_.immutable_data.fan_pwm_configs) { |
| json["FanPwmConfigs"].push_back(ProtoToJson(fan_pwm_config)); |
| } |
| |
| json["FanTachConfigs"] = nlohmann::json::array(); |
| for (const auto& fan_tach_config : |
| data_store_.immutable_data.fan_tach_configs) { |
| json["FanTachConfigs"].push_back(ProtoToJson(fan_tach_config)); |
| } |
| |
| json["SharedMemSensorConfigs"] = nlohmann::json::array(); |
| for (const auto& shared_mem_sensor_config : |
| data_store_.immutable_data.shared_mem_sensor_configs) { |
| json["SharedMemSensorConfigs"].push_back( |
| ProtoToJson(shared_mem_sensor_config)); |
| } |
| |
| json["IntelCpuSensorConfigs"] = nlohmann::json::array(); |
| for (const auto& intel_cpu_sensor_config : |
| data_store_.immutable_data.intel_cpu_sensor_configs) { |
| json["IntelCpuSensorConfigs"].push_back( |
| ProtoToJson(intel_cpu_sensor_config)); |
| } |
| |
| json["VirtualSensorConfigs"] = nlohmann::json::array(); |
| for (const auto& virtual_sensor_config : |
| data_store_.immutable_data.virtual_sensor_configs) { |
| json["VirtualSensorConfigs"].push_back(ProtoToJson(virtual_sensor_config)); |
| } |
| |
| const EntityConfigJsonImplMutableData* mutable_data = |
| data_store_.mutable_data.Get(); |
| |
| if (mutable_data != nullptr) { |
| json["TopologyConfig"] = ProtoToJson(mutable_data->topology_config); |
| json["FruTable"] = ProtoToJson(mutable_data->fru_table); |
| } |
| |
| json["CurrentStatus"] = mutable_data->parsed_status.ToString(); |
| |
| return json; |
| } |
| |
| EntityConfigJsonImpl::EntityConfigJsonImpl( |
| std::vector<nlohmann::json>&& config_list, const RawFruTable& fru_table, |
| size_t ad_hoc_fru_count) |
| : immutable_config_list_(std::move(config_list)), |
| data_store_(PackDataIntoStore( |
| ReloadConfig(fru_table, ad_hoc_fru_count, ReloadType::kReloadAll))) {} |
| |
| absl::StatusOr<std::unique_ptr<EntityConfig>> EntityConfigJsonImpl::Create( |
| std::vector<nlohmann::json>&& config_list, const RawFruTable& fru_table, |
| size_t ad_hoc_fru_count) { |
| auto entity_config = absl::WrapUnique(new EntityConfigJsonImpl( |
| std::move(config_list), fru_table, ad_hoc_fru_count)); |
| ECCLESIA_RETURN_IF_ERROR(entity_config->GetParsedStatus()); |
| return entity_config; |
| } |
| |
| absl::StatusOr<std::vector<nlohmann::json>> |
| EntityConfigJsonImpl::ParseJsonFilesIntoConfigList( |
| absl::string_view config_location) { |
| std::vector<nlohmann::json> config_list; |
| if (!std::filesystem::exists(config_location)) { |
| return config_list; |
| } |
| for (const auto& entry : |
| std::filesystem::directory_iterator(config_location)) { |
| if (entry.is_regular_file() && entry.path().extension() == ".json") { |
| std::string json_file_path = entry.path().string(); |
| std::ifstream json_file(json_file_path); |
| if (!json_file.is_open()) { |
| return absl::InternalError( |
| absl::StrCat("Failed to open file: ", json_file_path)); |
| } |
| |
| nlohmann::json parsed_config = |
| nlohmann::json::parse(json_file, nullptr, /*allow_exceptions=*/false, |
| /*ignore_comments=*/true); |
| if (parsed_config.is_discarded()) { |
| return absl::InvalidArgumentError( |
| absl::StrCat(json_file_path, " is illformed.")); |
| } |
| nlohmann::json::array_t config_as_array; |
| if (parsed_config.is_array()) { |
| config_as_array = parsed_config.get<nlohmann::json::array_t>(); |
| } else { |
| config_as_array.push_back(parsed_config); |
| } |
| |
| for (const auto& config : config_as_array) { |
| config_list.push_back(config); |
| } |
| } |
| } |
| return config_list; |
| } |
| |
| void EntityConfigJsonImpl::SetSmartRouter(RouterInterface* smart_router) { |
| smart_router_ = smart_router; |
| } |
| |
| void EntityConfigJsonImpl::SetSensorCollector( |
| SensorCollector* sensor_collector) { |
| sensor_collector_ = sensor_collector; |
| } |
| |
| absl::Status EntityConfigJsonImpl::GetParsedStatus() const { |
| return data_store_.mutable_data.Get()->parsed_status; |
| } |
| |
| EntityConfigJsonImplDataStore EntityConfigJsonImpl::PackDataIntoStore( |
| EntityConfigJsonImplData&& data) { |
| return EntityConfigJsonImplDataStore{ |
| .immutable_data = std::move(data.immutable_data), |
| .mutable_data = SimpleRcu<EntityConfigJsonImplMutableData>( |
| std::move(data.mutable_data), data.max_updates_to_mutable_data)}; |
| } |
| |
| } // namespace milotic_tlbmc |