| #include "tlbmc/configs/entity_config_json_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> |
| #include <fstream> |
| #include <memory> |
| #include <optional> |
| #include <queue> |
| #include <string> |
| #include <string_view> |
| #include <unordered_map> |
| #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/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 "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 "hwmon_temp_sensor_config.pb.h" |
| #include "i2c_common_config.pb.h" |
| #include "psu_sensor_config.pb.h" |
| #include "reading_range_config.pb.h" |
| #include "shared_mem_sensor_config.pb.h" |
| #include "threshold_config.pb.h" |
| #include "topology_config.pb.h" |
| #include "tlbmc/rcu/simple_rcu.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>; |
| |
| constexpr absl::string_view kRefreshIntervalKeyword = "RefreshIntervalMs"; |
| constexpr absl::string_view kQueueSizeKeyword = "QueueSize"; |
| |
| constexpr absl::string_view kCableDownstreamConnectionKeyword = |
| "CableDownstreamConnection"; |
| constexpr absl::string_view kCableUpstreamConnectionKeyword = |
| "CableUpstreamConnection"; |
| constexpr absl::string_view kDownstreamPortKeyword = "PortDownstream"; |
| constexpr absl::string_view kUpstreamConnectionKeyword = "UpstreamConnection"; |
| constexpr const char* kProbeKeyword = "ProbeV2"; |
| |
| constexpr absl::string_view kChassisTypeKeyword = "ChassisType"; |
| constexpr absl::string_view kStorageKeyword = "Storage"; |
| constexpr absl::string_view kProcessorKeyword = "Processor"; |
| 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 std::array<std::pair<std::string_view, SensorUnit>, 6> |
| kSupportedSensorUnits = { |
| // go/keep-sorted start |
| { |
| {"DegreeCelsius", SensorUnit::UNIT_DEGREE_CELSIUS}, |
| {"Watt", SensorUnit::UNIT_WATT}, |
| {"Ampere", SensorUnit::UNIT_AMPERE}, |
| {"Volt", SensorUnit::UNIT_VOLT}, |
| {"RPM", SensorUnit::UNIT_REVOLUTION_PER_MINUTE}, |
| {"Percent", SensorUnit::UNIT_PERCENT}, |
| } |
| // go/keep-sorted end |
| }; |
| |
| absl::Status ParseProcessorConfig(const nlohmann::json& config, |
| TopologyConfigNode& topology_config_node) { |
| const std::string* type_str = GetValueAsString(config, "Type"); |
| if (type_str == nullptr || *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"); |
| } |
| |
| topology_config_node.add_children_processor_ids(*processor_id); |
| return absl::OkStatus(); |
| } |
| |
| 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(); |
| } |
| |
| SensorUnit 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::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 && |
| *port_type_str != kCableDownstreamConnectionKeyword && |
| *port_type_str != kCableUpstreamConnectionKeyword)) { |
| 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); |
| port_config.set_port_type(PORT_TYPE_DOWNSTREAM); |
| if (*port_type_str == kUpstreamConnectionKeyword) { |
| if (topology_config_node.has_upstream_port_config()) { |
| return absl::InvalidArgumentError( |
| "Invalid config: Multiple Upstream connection configs present"); |
| } |
| *topology_config_node.mutable_upstream_port_config() = |
| 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); |
| topology_config_node.mutable_port_configs()->insert( |
| {*port_name, std::move(port_config)}); |
| } else if (*port_type_str == kCableUpstreamConnectionKeyword || |
| *port_type_str == kCableDownstreamConnectionKeyword) { |
| const std::string* port_label = GetValueAsString(config, "CableId"); |
| if (port_label == nullptr) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: CableId field is not found for cable ", |
| (*port_type_str == kCableUpstreamConnectionKeyword) ? "upstream" |
| : "downstream", |
| " connection")); |
| } |
| port_config.set_port_label(*port_label); |
| if (*port_type_str == kCableUpstreamConnectionKeyword) { |
| port_config.set_port_type(PORT_TYPE_CABLE_UPSTREAM); |
| *topology_config_node.mutable_upstream_cable_port_config() = |
| std::move(port_config); |
| } else { |
| port_config.set_port_type(PORT_TYPE_CABLE_DOWNSTREAM); |
| topology_config_node.mutable_cable_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>(); |
| } |
| |
| bool IsSubFru(const nlohmann::json& probe) { |
| const bool* is_sub_fru = GetValueAsBool(probe, "IsSubFru"); |
| if (is_sub_fru == nullptr) { |
| return false; |
| } |
| return *is_sub_fru; |
| } |
| |
| 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; |
| } |
| |
| 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)); |
| } |
| |
| absl::StatusOr<ProbedConfigMap> ProcessConfigs( |
| const std::vector<nlohmann::json>& config_list, FruTable& fru_table) { |
| ProbedConfigMap probed_config_map; |
| 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* true_str = probe_result->get_ptr<const std::string*>(); |
| bool true_value = true_str != nullptr && *true_str == "TRUE"; |
| // Check if the probe is a true value. |
| if (true_value) { |
| 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); |
| fru_table.mutable_key_to_fru()->insert({name, entity_object}); |
| continue; |
| } |
| bool skip_value = true_str != nullptr && *true_str == "FALSE"; |
| if (skip_value) { |
| 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>(); |
| // Wrap single IpmiFru object in array to simplify logic below. |
| if (!ipmi_fru_object.is_array()) { |
| ipmi_fru_object = nlohmann::json::array({ipmi_fru_object}); |
| } |
| 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* 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 && |
| 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 = false; |
| if ((bus == nullptr && address == nullptr) || |
| (bus != nullptr && *bus == fru.i2c_info().bus() && |
| address != nullptr && *address == fru.i2c_info().address())) { |
| i2c_info_matches_or_null = true; |
| } |
| |
| // 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 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 || !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. |
| 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; |
| } |
| // Sort the found FRU keys for each config. |
| if (auto probed_data_it = probed_config_map.find(name); |
| probed_data_it != probed_config_map.end()) { |
| std::sort(probed_data_it->second.fru_keys.begin(), |
| probed_data_it->second.fru_keys.end()); |
| } |
| } |
| } |
| return probed_config_map; |
| } |
| |
| 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_processor_ids()->pointer_begin(), |
| node.mutable_children_processor_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; |
| }); |
| } |
| } |
| |
| } // 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; |
| } |
| }; |
| |
| constexpr std::array<std::pair<std::string_view, HwmonTempSensorType>, 3> |
| kSupportedHwmonTempSensorTypes = { |
| // go/keep-sorted start numeric=yes |
| {{"MAX31725", HWMON_TEMP_SENSOR_TYPE1_MAX31725}, |
| {"MAX31732", HWMON_TEMP_SENSOR_TYPE3_MAX31732}, |
| {"TMP75", HWMON_TEMP_SENSOR_TYPE2_TMP75}}, |
| // go/keep-sorted end |
| }; |
| |
| constexpr std::array<std::pair<std::string_view, PsuSensorType>, 8> |
| kSupportedPsuSensorTypes = { |
| // go/keep-sorted start |
| {{"ADM1266", PSU_SENSOR_TYPE1_ADM1266}, |
| {"ADM1272", PSU_SENSOR_TYPE2_ADM1272}, |
| {"LTC2991", PSU_SENSOR_TYPE4_LTC2991}, |
| {"RAA228228", PSU_SENSOR_TYPE5_RAA228228}, |
| {"TDA38725", PSU_SENSOR_TYPE6_TDA38725}, |
| {"TDA38740", PSU_SENSOR_TYPE7_TDA38740}, |
| {"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 |
| }; |
| |
| 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; |
| } |
| |
| absl::Status EntityConfigJsonImpl::ParseAndPopulateConfig( |
| EntityConfigJsonImplImmutableData& data, const nlohmann::json& element, |
| absl::string_view config_name_with_index) { |
| const bool* tlbmc_owned = GetValueAsBool(element, "TlbmcOwned"); |
| if (tlbmc_owned == nullptr || !*tlbmc_owned) { |
| return absl::OkStatus(); |
| } |
| 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) { |
| absl::StatusOr<HwmonTempSensorConfig> hwmon_temp_sensor_config = |
| ParseHwmonTempSensorConfig(hwmon_temp_sensor_type, element); |
| if (!hwmon_temp_sensor_config.ok()) { |
| return hwmon_temp_sensor_config.status(); |
| } |
| *hwmon_temp_sensor_config->mutable_entity_common_config() |
| ->mutable_board_config_name() = config_name_with_index; |
| if (!hwmon_temp_sensor_config->entity_common_config().has_related_item()) { |
| // By default, if no related item is specified, the sensor related item |
| // will be its chassis |
| RelatedItem default_related_chassis; |
| default_related_chassis.set_id( |
| hwmon_temp_sensor_config->entity_common_config().board_config_name()); |
| default_related_chassis.set_type(RESOURCE_TYPE_BOARD); |
| *hwmon_temp_sensor_config->mutable_entity_common_config() |
| ->mutable_related_item() = default_related_chassis; |
| } |
| data.hwmon_temp_sensor_configs.push_back(*hwmon_temp_sensor_config); |
| } |
| |
| // PSU sensor. |
| PsuSensorType psu_sensor_type = IsPsuSensor(*type); |
| if (psu_sensor_type != PSU_SENSOR_TYPE0_UNKNOWN) { |
| absl::StatusOr<PsuSensorConfig> psu_sensor_config = |
| ParsePsuSensorConfig(psu_sensor_type, element); |
| if (!psu_sensor_config.ok()) { |
| return psu_sensor_config.status(); |
| } |
| *psu_sensor_config->mutable_entity_common_config() |
| ->mutable_board_config_name() = config_name_with_index; |
| if (!psu_sensor_config->entity_common_config().has_related_item()) { |
| // By default, if no related item is specified, the sensor related item |
| // will be its chassis |
| RelatedItem default_related_chassis; |
| default_related_chassis.set_id( |
| psu_sensor_config->entity_common_config().board_config_name()); |
| default_related_chassis.set_type(RESOURCE_TYPE_BOARD); |
| *psu_sensor_config->mutable_entity_common_config() |
| ->mutable_related_item() = default_related_chassis; |
| } |
| data.psu_sensor_configs.push_back(*psu_sensor_config); |
| } |
| |
| // FAN controller |
| FanControllerType fan_controller_type = IsFanController(*type); |
| if (fan_controller_type != FAN_CONTROLLER_TYPE_UNKNOWN) { |
| absl::StatusOr<FanControllerConfig> fan_controller_config = |
| ParseFanControllerConfig(fan_controller_type, element); |
| if (!fan_controller_config.ok()) { |
| return fan_controller_config.status(); |
| } |
| data.fan_controller_configs.push_back(*fan_controller_config); |
| } |
| |
| FanPwmType fan_pwm_type = IsFanPwm(*type); |
| if (fan_pwm_type != PWM_SENSOR_TYPE_UNKNOWN) { |
| absl::StatusOr<FanPwmConfig> fan_pwm_config = |
| ParseFanPwmConfig(fan_pwm_type, element); |
| if (!fan_pwm_config.ok()) { |
| return fan_pwm_config.status(); |
| } |
| *fan_pwm_config->mutable_entity_common_config() |
| ->mutable_board_config_name() = config_name_with_index; |
| data.fan_pwm_configs.push_back(*fan_pwm_config); |
| } |
| |
| FanTachType fan_tach_type = IsFanTach(*type); |
| if (fan_tach_type != TACH_SENSOR_TYPE_UNKNOWN) { |
| absl::StatusOr<FanTachConfig> fan_tach_config = |
| ParseFanTachConfig(fan_tach_type, element); |
| if (!fan_tach_config.ok()) { |
| return fan_tach_config.status(); |
| } |
| *fan_tach_config->mutable_entity_common_config() |
| ->mutable_board_config_name() = config_name_with_index; |
| data.fan_tach_configs.push_back(*fan_tach_config); |
| } |
| |
| SharedMemSensorType shared_mem_sensor_type = IsSharedMemSensor(*type); |
| if (shared_mem_sensor_type != SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN) { |
| absl::StatusOr<SharedMemSensorConfig> shared_mem_sensor_config = |
| ParseSharedMemSensorConfig(shared_mem_sensor_type, element); |
| if (!shared_mem_sensor_config.ok()) { |
| return shared_mem_sensor_config.status(); |
| } |
| *shared_mem_sensor_config->mutable_entity_common_config() |
| ->mutable_board_config_name() = config_name_with_index; |
| data.shared_mem_sensor_configs.push_back(*shared_mem_sensor_config); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| void EntityConfigJsonImpl::UpdateFruAndTopology(const RawFruTable& fru_table) { |
| 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(); |
| } |
| } |
| |
| 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<ProbedConfigMap> probed_config_map = |
| ProcessConfigs(immutable_config_list_, mutable_data.fru_table); |
| if (!probed_config_map.ok()) { |
| mutable_data.parsed_status = probed_config_map.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 (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] : *probed_config_map) { |
| // Check if config belongs to a sub FRU. |
| // We have already checked that the config has a probe in `ProcessConfigs`. |
| bool is_subfru = IsSubFru(config_data.config.at(kProbeKeyword)); |
| |
| // 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); |
| |
| const std::string fru_key = config_data.fru_keys[i].ToString(); |
| |
| 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}; |
| } |
| |
| // 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()->set_chassis_type( |
| ParseChassisType(config_data.config)); |
| } |
| |
| ParsePartLocationType(config_data.config, topology_config_node); |
| ParseRootChassisLocationCode(config_data.config, topology_config_node); |
| if (is_subfru && |
| topology_config_node.location_context().location_type() != |
| PART_LOCATION_TYPE_EMBEDDED) { |
| mutable_data.parsed_status = absl::InternalError(absl::StrCat( |
| "Invalid config: Sub FRU ", topology_config_node.name(), |
| " has part location type ", |
| topology_config_node.location_context().location_type(), |
| ". All sub FRUs should have part location type " |
| "PART_LOCATION_TYPE_EMBEDDED.")); |
| } |
| |
| absl::StatusOr<nlohmann::json> exposes = |
| GetObject(config_data.config, "Exposes"); |
| if (!exposes.ok()) { |
| mutable_data.parsed_status = exposes.status(); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| const auto* elements = exposes->get_ptr<const nlohmann::json::array_t*>(); |
| if (elements == nullptr) { |
| mutable_data.parsed_status = absl::InvalidArgumentError(absl::StrCat( |
| "Invalid config: The JSON field `Exposes` is not an array", name)); |
| 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")) { |
| element["Bus"] = fru_info.i2c_info().bus(); |
| } |
| |
| // 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); |
| 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(element, topology_config_node); |
| 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_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); |
| if (!is_subfru) { |
| mutable_data.topology_config.mutable_fru_configs()->insert( |
| {fru_key, config_name_with_index}); |
| } else { |
| topology_config_node.mutable_fru_info()->set_is_sub_fru(true); |
| fru_key_to_sub_fru_config[fru_key].insert(config_name_with_index); |
| } |
| |
| if (fru_info.attributes().resource_type() == RESOURCE_TYPE_CABLE) { |
| 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.has_upstream_port_config() || |
| !topology_config_node.port_configs().empty() || |
| topology_config_node.has_upstream_cable_port_config() || |
| !topology_config_node.cable_port_configs().empty()) { |
| 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_names] : fru_key_to_sub_fru_config) { |
| for (const auto& config_name : config_names) { |
| auto it = |
| mutable_data.topology_config.mutable_fru_configs()->find(fru_key); |
| if (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_name)); |
| return EntityConfigJsonImplData{ |
| .immutable_data = std::move(immutable_data), |
| .mutable_data = std::move(mutable_data), |
| .max_updates_to_mutable_data = ad_hoc_fru_count}; |
| } |
| std::string config_name_with_index = it->second; |
| // 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_name_with_index); |
| *topology_config_node.mutable_sub_fru_config_names()->Add() = config_name; |
| } |
| } |
| |
| 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().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_names] : |
| fru_key_to_sub_fru_config) { |
| fru_keys_with_sub_frus.insert(sub_fru_config_names.begin(), |
| sub_fru_config_names.end()); |
| } |
| |
| absl::Status create_associations_status = |
| CreateAssociationsBetweenTopologyConfigNodes( |
| mutable_data.topology_config, mutable_data.fru_table, |
| std::move(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); |
| |
| 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; |
| } |
| |
| std::unordered_map<std::string, ReadingRangeConfigs> |
| EntityConfigJsonImpl::ParseReadingRangeConfigs( |
| const nlohmann::json& config, absl::Span<const std::string> labels) { |
| std::unordered_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::StatusOr<I2cCommonConfig> EntityConfigJsonImpl::ParseI2cCommonConfig( |
| const nlohmann::json& config) { |
| I2cCommonConfig i2c_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())); |
| } |
| i2c_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: "); |
| } |
| i2c_config.set_address(address_uint64); |
| return i2c_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<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<std::unordered_map<std::string, ThresholdConfigs>> |
| EntityConfigJsonImpl::ParseThresholdConfigs(const nlohmann::json& config) { |
| std::unordered_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<std::unordered_map<std::string, std::string>> |
| EntityConfigJsonImpl::ParseLabelToName(const nlohmann::json& config, |
| absl::Span<const std::string> labels, |
| bool ignore_zero_index_name) { |
| std::unordered_map<std::string, std::string> label_to_name; |
| for (std::size_t i = 0; i < labels.size(); ++i) { |
| const std::string& label = labels[i]; |
| std::string key = absl::Substitute("$0_Name", 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] = *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) { |
| 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_i2c_common_config(), |
| ParseI2cCommonConfig(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; |
| } |
| std::unordered_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()); |
| |
| return hwmon_temp_sensor_config; |
| } |
| |
| absl::StatusOr<FanControllerConfig> |
| EntityConfigJsonImpl::ParseFanControllerConfig(FanControllerType type, |
| const nlohmann::json& config) { |
| 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_i2c_common_config(), |
| ParseI2cCommonConfig(config)); |
| |
| return fan_controller_config; |
| } |
| |
| absl::StatusOr<PsuSensorConfig> EntityConfigJsonImpl::ParsePsuSensorConfig( |
| PsuSensorType type, const nlohmann::json& config) { |
| 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_i2c_common_config(), |
| ParseI2cCommonConfig(config)); |
| ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, psu_sensor_config)); |
| |
| std::vector<std::string> labels; |
| ECCLESIA_ASSIGN_OR_RETURN(labels, ParseLabels(config)); |
| |
| std::unordered_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()); |
| |
| std::unordered_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()); |
| |
| std::unordered_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()); |
| |
| std::optional<RelatedItem> related_item = ParseRelatedItem(config); |
| if (related_item.has_value()) { |
| *psu_sensor_config.mutable_entity_common_config()->mutable_related_item() = |
| *related_item; |
| } |
| |
| return psu_sensor_config; |
| } |
| |
| absl::StatusOr<FanPwmConfig> EntityConfigJsonImpl::ParseFanPwmConfig( |
| FanPwmType type, const nlohmann::json& config) { |
| 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_i2c_common_config(), |
| ParseI2cCommonConfig(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; |
| } |
| |
| return fan_pwm_config; |
| } |
| |
| absl::StatusOr<FanTachConfig> EntityConfigJsonImpl::ParseFanTachConfig( |
| FanTachType type, const nlohmann::json& config) { |
| 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_i2c_common_config(), |
| ParseI2cCommonConfig(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)); |
| |
| std::unordered_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; |
| } |
| |
| return fan_tach_config; |
| } |
| |
| absl::StatusOr<SharedMemSensorConfig> |
| EntityConfigJsonImpl::ParseSharedMemSensorConfig(SharedMemSensorType type, |
| const nlohmann::json& config) { |
| 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)); |
| |
| std::unordered_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)); |
| |
| return shared_mem_sensor_config; |
| } |
| |
| absl::Status EntityConfigJsonImpl::CreateAssociationsBetweenTopologyConfigNodes( |
| TopologyConfig& topology_config, FruTable& fru_table, |
| absl::flat_hash_set<absl::string_view> expected_nodes_traversed) { |
| // Find root node in TopologyConfig. |
| std::string root_chassis_location_code; |
| for (const auto& [config_name, 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.has_upstream_port_config() && |
| !topology_config_node.has_upstream_cable_port_config()) { |
| if (topology_config.has_root_node_name()) { |
| return absl::InternalError("Invalid config: Multiple root nodes found"); |
| } |
| topology_config.set_root_node_name(config_name); |
| } |
| } |
| |
| if (!topology_config.has_root_node_name()) { |
| return absl::InternalError("Invalid config: No root node found"); |
| } |
| |
| absl::flat_hash_map<std::string, std::string> |
| upstream_cable_port_name_to_config_name; |
| // Map of port name to config name. |
| absl::flat_hash_map<std::string, std::string> |
| upstream_port_name_to_config_name; |
| for (const auto& [unused, topology_config_node] : |
| topology_config.topology_config_nodes()) { |
| if (!topology_config_node.upstream_cable_port_config() |
| .port_name() |
| .empty()) { |
| upstream_cable_port_name_to_config_name |
| [topology_config_node.upstream_cable_port_config().port_name()] = |
| topology_config_node.name(); |
| } |
| upstream_port_name_to_config_name |
| [topology_config_node.upstream_port_config().port_name()] = |
| topology_config_node.name(); |
| } |
| |
| // 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_name()); |
| 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(); |
| |
| std::queue<TopologyConfigNode*> node_queue; |
| node_queue.push(&root_node); |
| absl::flat_hash_set<absl::string_view> unexpected_nodes_traversed; |
| while (!node_queue.empty()) { |
| TopologyConfigNode* current_node = node_queue.front(); |
| node_queue.pop(); |
| if (expected_nodes_traversed.contains(current_node->name())) { |
| expected_nodes_traversed.erase(current_node->name()); |
| } else { |
| unexpected_nodes_traversed.insert(current_node->name()); |
| } |
| 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_name = |
| upstream_port_name_to_config_name.find(port_name); |
| if (find_downstream_node_name == |
| upstream_port_name_to_config_name.end()) { |
| continue; |
| } |
| absl::string_view downstream_node_name = |
| find_downstream_node_name->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_name); |
| // If node is Sub-Fru then we add :device to the devpath and then append |
| // the port label. |
| if (downstream_node.fru_info().is_sub_fru()) { |
| 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())); |
| } |
| // 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()); |
| downstream_node.set_parent_resource_id(current_node->name()); |
| if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) { |
| current_node->add_children_chassis_ids(downstream_node.name()); |
| } |
| node_queue.push(&downstream_node); |
| } |
| |
| for (const auto& [port_name, port_config] : |
| current_node->cable_port_configs()) { |
| auto find_downstream_node_name = |
| upstream_cable_port_name_to_config_name.find(port_name); |
| if (find_downstream_node_name == |
| upstream_cable_port_name_to_config_name.end()) { |
| continue; |
| } |
| absl::string_view downstream_node_name = |
| find_downstream_node_name->second; |
| TopologyConfigNode& downstream_node = |
| topology_config.mutable_topology_config_nodes()->at( |
| downstream_node_name); |
| auto find_cable_fru = |
| topology_config.mutable_topology_config_nodes()->find( |
| port_config.port_label()); |
| if (find_cable_fru == |
| topology_config.mutable_topology_config_nodes()->end()) { |
| return absl::InternalError(absl::StrCat( |
| "Invalid config: Cable config name does not match cable connection " |
| "CableId: ", |
| port_config.port_label(), " cannot populate this cable.")); |
| } |
| TopologyConfigNode& cable_topology_node = find_cable_fru->second; |
| if (current_fru.attributes().resource_type() != RESOURCE_TYPE_CABLE) { |
| downstream_node.mutable_location_context()->set_devpath( |
| absl::StrCat(current_node->mutable_location_context()->devpath(), |
| "/", "DOWNLINK")); |
| node_queue.push(&downstream_node); |
| cable_topology_node.set_parent_resource_id(current_node->name()); |
| current_node->add_children_cable_ids(cable_topology_node.name()); |
| } else { |
| downstream_node.add_children_cable_ids(cable_topology_node.name()); |
| } |
| } |
| } |
| |
| if (!expected_nodes_traversed.empty() || |
| !unexpected_nodes_traversed.empty()) { |
| 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(expected_nodes_traversed, ", "), |
| absl::StrJoin(unexpected_nodes_traversed, ", "))); |
| } |
| return absl::OkStatus(); |
| } |
| |
| 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_name) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| const auto fru_config_it = |
| topology_config->topology_config_nodes().find(config_name); |
| if (fru_config_it == topology_config->topology_config_nodes().end()) { |
| return absl::NotFoundError( |
| absl::StrCat("Config name not found: ", config_name)); |
| } |
| 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::GetAllConfigNames() const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| std::vector<std::string> config_names; |
| for (const auto& [config_name, topology_config_node] : |
| topology_config->topology_config_nodes()) { |
| if (!topology_config_node.not_owned_by_tlbmc()) { |
| config_names.push_back(config_name); |
| } |
| } |
| return config_names; |
| } |
| |
| absl::StatusOr<std::string> EntityConfigJsonImpl::GetConfigNameByFruKey( |
| 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::GetFruKeyByConfigName( |
| absl::string_view config_name) const { |
| ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config, |
| GetTopologyConfig()); |
| auto fru_config_it = |
| topology_config->topology_config_nodes().find(config_name); |
| if (fru_config_it == topology_config->topology_config_nodes().end()) { |
| return absl::NotFoundError( |
| absl::StrCat("Config name not found: ", config_name)); |
| } |
| 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)); |
| } |
| |
| 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 absl::NotFoundError( |
| absl::StrCat("Config location not found: ", config_location)); |
| } |
| 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; |
| } |
| |
| 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 |