blob: 06eb20ad39f9d9505ec12212a8c65277a2d4237a [file] [log] [blame]
#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