blob: 6f3a51c4eabc4499d0346b9912356db1d16cac4c [file] [edit]
#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 <iterator>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "one/public_offline_node_entities.h"
#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.hpp"
#include "nlohmann/json_fwd.hpp"
#include "json_utils.h"
#include "tlbmc/central_config/config.h"
#include "tlbmc/collector/sensor_collector.h"
#include "adc_sensor_config.pb.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 "gpio_sensor_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 "redfish_aggregated_batch_config.pb.h"
#include "redfish_aggregated_sensor_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 "google/protobuf/descriptor.h"
namespace milotic_tlbmc {
using ::production_msv::node_entities::ReadOfflineNodeEntityInformation;
using ::production_msv::node_entities_proto::OfflineNodeEntityInformation;
namespace {
using ::crow::RouterInterface;
using ::milotic::authz::GetValueAsArray;
using ::milotic::authz::GetValueAsBool;
using ::milotic::authz::GetValueAsDoubleFromFloatOrInteger;
using ::milotic::authz::GetValueAsInt;
using ::milotic::authz::GetValueAsJson;
using ::milotic::authz::GetValueAsString;
using ::milotic::authz::GetValueAsUint;
using ::milotic::authz::GetValueAsUintFromStringOrInteger;
using ::google::protobuf::Descriptor;
using ::google::protobuf::FieldDescriptor;
using ::google::protobuf::Message;
using ::google::protobuf::Reflection;
// 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";
constexpr absl::string_view kPowerSupplyDownstreamKeyword =
"PowerSupplyPortDownstream";
constexpr absl::string_view kPowerSupplyUpstreamKeyword =
"PowerSupplyPortUpstream";
// 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 kNotProbePrefix = "NOT(";
constexpr absl::string_view kOfflineNodeEntityInfoKeyword =
"OFFLINE_NODE_ENTITY_INFO";
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 kManagerKeyword = "Manager";
constexpr absl::string_view kComputerSystemKeyword = "ComputerSystem";
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 absl::string_view kAllKeyword = "All";
constexpr absl::string_view kRedfishHiddenKeyword = "RedfishHidden";
// from google3/third_party/openbmc_entity_manager/schemas/legacy.json
constexpr std::array<std::pair<std::string_view, SensorUnit>, 10>
kSupportedSensorUnits = {{
// go/keep-sorted start
{"Amperes", SensorUnit::UNIT_AMPERE},
{"Binary", SensorUnit::UNIT_BINARY},
{"Count", SensorUnit::UNIT_COUNT},
{"DegreesC", SensorUnit::UNIT_DEGREE_CELSIUS},
{"Joules", SensorUnit::UNIT_JOULES},
{"Pascals", SensorUnit::UNIT_PASCAL},
{"Percent", SensorUnit::UNIT_PERCENT},
{"RPMS", SensorUnit::UNIT_REVOLUTION_PER_MINUTE},
{"Volts", SensorUnit::UNIT_VOLT},
{"Watts", SensorUnit::UNIT_WATT},
// go/keep-sorted end
}};
constexpr auto kIpmiFruProbeStringFields = std::to_array<absl::string_view>({
// go/keep-sorted start
"BOARD_INFO_AM1",
"BOARD_INFO_AM2",
"BOARD_INFO_AM3",
"BOARD_INFO_AM4",
"BOARD_PART_NUMBER",
"BOARD_PRODUCT_NAME",
"PRODUCT_INFO_AM2",
"PRODUCT_INFO_AM3",
"PRODUCT_PART_NUMBER",
"PRODUCT_PRODUCT_NAME",
// go/keep-sorted end
});
absl::StatusOr<std::string> SubstituteExpressionVariables(
absl::string_view base, absl::string_view target, size_t value) {
std::string uppercase_var = absl::AsciiStrToUpper(target);
std::string lowercase_var = absl::AsciiStrToLower(target);
if (absl::StrContainsIgnoreCase(base, lowercase_var)) {
return EvaluateStrExpressionSubstitution(
base,
absl::StrContains(base, lowercase_var) ? lowercase_var : uppercase_var,
absl::StrCat(value));
}
return std::string(base);
}
absl::Status ParseStorageConfig(const nlohmann::json& config,
TopologyConfigNode& topology_config_node,
size_t index, size_t bus) {
const nlohmann::json* storage_config_json =
GetValueAsJson(config, kStorageKeyword);
if (storage_config_json == nullptr) {
return absl::OkStatus();
}
// We can have multiple storage configs.
nlohmann::json::array_t storage_configs;
if (storage_config_json->is_array()) {
storage_configs = *storage_config_json;
} else if (storage_config_json->is_object()) {
storage_configs.push_back(*storage_config_json);
}
for (const auto& storage_config : storage_configs) {
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");
}
absl::StatusOr<std::string> substituted_id =
SubstituteExpressionVariables(*storage_id, "$index", index + 1);
if (!substituted_id.ok()) {
return substituted_id.status();
}
substituted_id =
SubstituteExpressionVariables(*substituted_id, "$bus", bus);
if (!substituted_id.ok()) {
return substituted_id.status();
}
const std::string* storage_link_type_str =
GetValueAsString(storage_config, "StorageLinkType");
StorageLinkType storage_link_type = STORAGE_LINK_TYPE_SYSTEMS;
StorageIdInfo* new_storage =
topology_config_node.add_children_storage_ids();
if (storage_link_type_str != nullptr &&
StorageLinkType_Parse(*storage_link_type_str, &storage_link_type)) {
new_storage->mutable_link_config()->set_link_type(storage_link_type);
}
new_storage->set_id(*substituted_id);
}
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();
}
absl::Status ParsePowerPortConfig(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 != kPowerSupplyUpstreamKeyword &&
*port_type_str != kPowerSupplyDownstreamKeyword)) {
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 or valid for port");
}
PortConfig port_config;
port_config.set_port_name(*port_name);
if (*port_type_str == kPowerSupplyUpstreamKeyword) {
if (std::any_of(topology_config_node.upstream_power_port_configs().cbegin(),
topology_config_node.upstream_power_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 Power connection "
"configs present for ",
*port_name));
}
port_config.set_port_type(PORT_TYPE_POWER_UPSTREAM);
*topology_config_node.add_upstream_power_port_configs() =
std::move(port_config);
} else if (*port_type_str == kPowerSupplyDownstreamKeyword) {
port_config.set_port_type(PORT_TYPE_POWER_DOWNSTREAM);
topology_config_node.mutable_power_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::StatusOr<absl::flat_hash_map<std::string, RelatedItem>>
ParseRelatedItemMap(const nlohmann::json& config) {
absl::flat_hash_map<std::string, RelatedItem> sensor_name_to_related_item;
if (!config.contains(kRelatedItemKeyword)) {
return sensor_name_to_related_item;
}
const nlohmann::json::array_t* related_item_configs =
GetValueAsArray(config, kRelatedItemKeyword);
if (related_item_configs == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: RelatedItem field is not an array");
}
for (const auto& related_item_config : *related_item_configs) {
const std::string* sensor_name =
GetValueAsString(related_item_config, "SensorName");
if (sensor_name == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid RelatedItem config: SensorName field is not a "
"string or missing "
"for related item config: ",
related_item_config.dump()));
}
const std::string* resource_id =
GetValueAsString(related_item_config, "ResourceId");
if (resource_id == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid RelatedItem config: No ResourceId found for "
"related item config: ",
related_item_config.dump()));
}
const std::string* item_type_str =
GetValueAsString(related_item_config, "Type");
ResourceType resource_type;
if (item_type_str == nullptr ||
!ResourceType_Parse(*item_type_str, &resource_type)) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid RelatedItem config: Invalid Type "
"found for config: ",
related_item_config.dump()));
}
RelatedItem related_item;
related_item.set_id(*resource_id);
related_item.set_type(resource_type);
sensor_name_to_related_item[*sensor_name] = related_item;
}
return sensor_name_to_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);
}
}
const std::string* version_ptr = GetValueAsString(asset_config, "Version");
if (version_ptr != nullptr) {
if (absl::AsciiStrToUpper(*version_ptr) == "$PRODUCT_VERSION" &&
fru_object.data().fru_info().has_product_version()) {
asset.set_version(fru_object.data().fru_info().product_version());
} else if (absl::StartsWith(*version_ptr, "$")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid Asset config, cannot substitute ", *version_ptr,
". Check this is a valid substitution keyword and scanned Fru "
"contains this field."));
} else {
asset.set_version(*version_ptr);
}
}
const std::string* asset_tag_ptr = GetValueAsString(asset_config, "AssetTag");
if (asset_tag_ptr != nullptr) {
if (absl::AsciiStrToUpper(*asset_tag_ptr) == "$PRODUCT_ASSET_TAG" &&
fru_object.data().fru_info().has_product_asset_tag()) {
asset.set_asset_tag(fru_object.data().fru_info().product_asset_tag());
} else if (absl::StartsWith(*asset_tag_ptr, "$")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid Asset config, cannot substitute ", *asset_tag_ptr,
". Check this is a valid substitution keyword and scanned Fru "
"contains this field."));
} else {
asset.set_asset_tag(*asset_tag_ptr);
}
}
*fru_object.mutable_data()->mutable_asset_info() = asset;
return absl::OkStatus();
}
absl::Status ParsePowerSupplyConfig(const nlohmann::json& config,
Fru& fru_object) {
const nlohmann::json* power_supply_config_ptr =
GetValueAsJson(config, "PowerSupply");
if (power_supply_config_ptr == nullptr) {
return absl::OkStatus();
}
PowerSupplyInfo power_supply_info;
nlohmann::json power_supply_config = *power_supply_config_ptr;
const std::string* input_sensor_name_ptr =
GetValueAsString(power_supply_config, "InputSensorName");
if (input_sensor_name_ptr == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid PowerSupply config: InputSensorName field is not "
"a string or missing for power supply config: ",
power_supply_config.dump()));
}
power_supply_info.set_input_sensor_name(*input_sensor_name_ptr);
*fru_object.mutable_data()->mutable_power_supply_info() = power_supply_info;
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 EvaluateProbeRegexMatch(const std::string& fru_field,
const std::string& probe_field) {
if (absl::StartsWith(probe_field, kNotProbePrefix) &&
absl::EndsWith(probe_field, ")")) {
return !RE2::FullMatch(
fru_field,
probe_field.substr(kNotProbePrefix.size(),
probe_field.size() - (kNotProbePrefix.size() + 1)));
}
return RE2::FullMatch(fru_field, probe_field);
}
absl::StatusOr<bool> CheckOfflineNodeEntityInfoMatch(
const nlohmann::json& one_probe_config,
const Message& offline_node_entities) {
const Descriptor* descriptor = offline_node_entities.GetDescriptor();
const Reflection* reflection = offline_node_entities.GetReflection();
// To enforce AND behavior for all regex fields defined in this ONE Probe
// config, we must iterate over all json fields, return false early if we know
// that the regex fields do not match.
for (auto const& [key, value] : one_probe_config.items()) {
const FieldDescriptor* field = descriptor->FindFieldByName(key);
if (field == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: '", key,
"' field is not found at this level in OfflineNodeEntities"));
}
// If the value is an object, we need to check if the field is a message
// in OfflineNodeEntities. If it is, then we need to recursively check the
// equality of the two fields.
if (value.is_object()) {
if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
return absl::InvalidArgumentError(
"Field '" + key +
"' is an object in config but not a message in OfflineNodeEntities "
"data");
}
if (field->is_repeated()) {
// Repeated field found, we need to check all of them, but exit as long
// as one matches.
bool match_found = false;
for (int i = 0; i < reflection->FieldSize(offline_node_entities, field);
++i) {
ECCLESIA_ASSIGN_OR_RETURN(
bool result, CheckOfflineNodeEntityInfoMatch(
value, reflection->GetRepeatedMessage(
offline_node_entities, field, i)));
if (result) {
match_found = true;
break;
}
}
if (!match_found) {
return false;
}
continue;
}
// Single field found, check if it matches.
ECCLESIA_ASSIGN_OR_RETURN(
bool result,
CheckOfflineNodeEntityInfoMatch(
value, reflection->GetMessage(offline_node_entities, field)));
if (!result) {
return false;
}
continue;
}
// If the value is a string, we need to check if the field is a string in
// OfflineNodeEntities. If it is, then we need to check if the string
// matches the regex string in the config.
if (value.is_string()) {
if (field->cpp_type() != FieldDescriptor::CPPTYPE_STRING) {
return absl::InvalidArgumentError("Field '" + key +
"' is a string in config but not a "
"string in OfflineNodeEntities data");
}
std::string proto_value =
reflection->GetString(offline_node_entities, field);
std::string probe_value = value.get<std::string>();
if (!EvaluateProbeRegexMatch(proto_value, probe_value)) {
return false;
}
} else {
return absl::InvalidArgumentError(
"Unsupported OfflineNodeEntities Probe value type for field '" + key +
"'");
}
}
return true;
}
bool CheckProbeFieldMatch(const std::string* probe_field, const Fru& fru,
absl::string_view fru_field_name) {
if (probe_field == nullptr) {
return true;
}
auto find_fru_field = fru.data().fields().find(fru_field_name);
if (find_fru_field == fru.data().fields().end()) {
return false;
}
return EvaluateProbeRegexMatch(find_fru_field->second, *probe_field);
}
// 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, const FruTable& fru_table,
ProbedConfigMap& probed_config_map,
const OfflineNodeEntityInformation& offline_node_entities) {
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& [key, fru] : fru_table.key_to_fru()) {
// We should only be probing raw fru which have the pattern $bus:$address
if (!RE2::FullMatch(key, R"(^\d+:0x[0-9a-f]+$)") &&
!RE2::FullMatch(key, R"(^\d+:\d+$)")) {
LOG(INFO) << "Skipping non-raw fru: " << key;
continue;
}
bool fru_matched_any_probe = false;
for (const auto& ipmi_fru : ipmi_fru_object) {
bool has_any_probe_field = false;
for (const auto& field : kIpmiFruProbeStringFields) {
if (GetValueAsString(ipmi_fru, field) != nullptr) {
has_any_probe_field = true;
break;
}
}
std::optional<uint64_t> bus =
GetValueAsUintFromStringOrInteger(ipmi_fru, "BUS");
std::optional<uint64_t> address =
GetValueAsUintFromStringOrInteger(ipmi_fru, "ADDRESS");
if (!has_any_probe_field && !bus.has_value() && !address.has_value()) {
return absl::InvalidArgumentError(
"Invalid config: IpmiFru field is not a valid probe");
}
bool all_string_fields_match = true;
for (const auto& field : kIpmiFruProbeStringFields) {
if (!CheckProbeFieldMatch(GetValueAsString(ipmi_fru, field), fru,
field)) {
all_string_fields_match = false;
break;
}
}
bool bus_matches_or_null =
!bus.has_value() || bus.value() == fru.i2c_info().bus();
bool address_matches_or_null =
!address.has_value() || address.value() == fru.i2c_info().address();
// If the FRU doesn't match any probe, continue to the next FRU.
if (!bus_matches_or_null || !address_matches_or_null ||
!all_string_fields_match) {
continue;
}
fru_matched_any_probe = true;
break;
}
if (!fru_matched_any_probe) {
continue;
}
// This condition will check that
// 1. Only configs containing in $index or $bus can have multiple Fru
// probe true
// 2. Multiple configs cannot probe true with the same name
const bool* compound_fru_ptr = GetValueAsBool(config, "CompoundFru");
bool compound_fru = compound_fru_ptr != nullptr && *compound_fru_ptr;
if (probed_config_map.contains(name) &&
!absl::StrContains(name, "$index") &&
!absl::StrContainsIgnoreCase(name, "$bus") && !compound_fru) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Frus match the same config multiple"
" times but config name does not contain $index or "
"$bus: ",
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,
bool enable_deterministic_bmc,
const OfflineNodeEntityInformation& offline_node_entities) {
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, {{" ", "_"}});
// Check for keyword based probes.
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;
}
}
// Perform json object based probes.
nlohmann::json probe = *probe_result;
// If probe is OfflineNodeEntityInfo, create a ready FRU object for this.
auto offline_node_entity_info_it =
probe.find(kOfflineNodeEntityInfoKeyword);
if (offline_node_entity_info_it != probe.end()) {
ECCLESIA_ASSIGN_OR_RETURN(
bool offline_node_entity_info_match,
CheckOfflineNodeEntityInfoMatch(
offline_node_entity_info_it->get<nlohmann::json>(),
offline_node_entities));
if (!offline_node_entity_info_match) {
continue;
}
auto& probed_data = probed_config_map[name];
probed_data.fru_keys.push_back(FruKey(name));
probed_data.config = config;
Fru entity_object;
entity_object.mutable_attributes()->set_key(name);
entity_object.mutable_attributes()->set_status(STATUS_READY);
fru_table.mutable_key_to_fru()->insert({name, entity_object});
continue;
}
// 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,
offline_node_entities);
if (!is_fru_probed.ok()) {
return is_fru_probed.status();
}
// If the config is supposed to attach to an existing fru, we need to create
// copies of the fru for each config so they can independently have
// different asset fields.
if (probe.contains("AttachToExistingFru") && *is_fru_probed) {
std::vector<FruKey> new_fru_keys;
auto& probed_data = probed_config_map[name];
for (size_t i = 0; i < probed_data.fru_keys.size(); i++) {
std::string config_name_with_index = name;
absl::StrReplaceAll({{"$index", std::to_string(i + 1)}},
&config_name_with_index);
FruKey fru_key = probed_data.fru_keys[i];
Fru fru_copy = fru_table.key_to_fru().at(fru_key.ToString());
fru_copy.mutable_attributes()->set_key(config_name_with_index);
fru_table.mutable_key_to_fru()->insert(
{config_name_with_index, std::move(fru_copy)});
new_fru_keys.push_back(FruKey(config_name_with_index));
}
probed_config_map[name].fru_keys = std::move(new_fru_keys);
}
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, config_data] : probed_config_map) {
if (pending_config_name_to_probed_configs.contains(name)) {
found_target_candidates.push(name);
continue;
}
if (absl::StrContains(name, "$index")) {
for (size_t i = 0; i < config_data.fru_keys.size(); ++i) {
std::string config_name_with_index = name;
absl::StrReplaceAll({{"$index", std::to_string(i + 1)}},
&config_name_with_index);
if (pending_config_name_to_probed_configs.contains(
config_name_with_index)) {
found_target_candidates.push(config_name_with_index);
}
}
}
if (absl::StrContainsIgnoreCase(name, "$bus")) {
for (const auto& fru_key : config_data.fru_keys) {
std::string config_name_with_bus = name;
absl::StrReplaceAll({{"_", " "}}, &config_name_with_bus);
absl::StatusOr<std::string> bus_value_substitute_result =
SubstituteExpressionVariables(config_name_with_bus, "$bus",
fru_key.bus);
if (bus_value_substitute_result.ok()) {
config_name_with_bus = *bus_value_substitute_result;
absl::StrReplaceAll({{" ", "_"}}, &config_name_with_bus);
if (pending_config_name_to_probed_configs.contains(
config_name_with_bus)) {
found_target_candidates.push(config_name_with_bus);
}
}
}
}
}
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);
// Assign FruData from the parent Fru in the FOUND probe. This is needed
// for population of Asset info for some modeling where multiple
// separate Fru objects are modeled from same physical board.
// TODO b/483380675 - If multiple FOUND is supported, we need to handle
if (auto it = probed_config_map.find(name);
it != probed_config_map.end() && !it->second.fru_keys.empty()) {
const std::string source_fru_key = it->second.fru_keys[0].ToString();
if (auto fru_it = fru_table.key_to_fru().find(source_fru_key);
fru_it != fru_table.key_to_fru().end()) {
*entity_object.mutable_data() = fru_it->second.data();
}
}
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 StorageIdInfo* id_1, const StorageIdInfo* id_2) {
return id_1->id() < id_2->id();
});
std::sort(node.mutable_children_assembly_ids()->pointer_begin(),
node.mutable_children_assembly_ids()->pointer_end(),
[](const std::string* id_1, const std::string* id_2) {
return *id_1 < *id_2;
});
}
}
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 {
// Have empty devpath check because some platforms have hacky devpath
// generation.
const std::string& l = lhs->location_context().devpath();
const std::string& r = rhs->location_context().devpath();
if (l.empty() && !r.empty()) return true;
if (!l.empty() && r.empty()) return false;
return l > r;
}
};
constexpr std::array<std::pair<std::string_view, HwmonTempSensorType>, 7>
kSupportedHwmonTempSensorTypes = {{
// go/keep-sorted start
{"MAX31725", HWMON_TEMP_SENSOR_TYPE1_MAX31725},
{"MAX31732", HWMON_TEMP_SENSOR_TYPE3_MAX31732},
{"SA56004", HWMON_TEMP_SENSOR_TYPE4_SA56004},
{"TMP421", HWMON_TEMP_SENSOR_TYPE7_TMP421},
{"TMP431", HWMON_TEMP_SENSOR_TYPE6_TMP431},
{"TMP461", HWMON_TEMP_SENSOR_TYPE5_TMP461},
{"TMP75", HWMON_TEMP_SENSOR_TYPE2_TMP75},
// go/keep-sorted end
}};
constexpr auto kSupportedPsuSensorTypes =
std::to_array<std::pair<std::string_view, PsuSensorType>>({
// go/keep-sorted start
{"ADM1266", PSU_SENSOR_TYPE1_ADM1266},
{"ADM1272", PSU_SENSOR_TYPE2_ADM1272},
{"ADM1273", PSU_SENSOR_TYPE23_ADM1273},
{"BMR490", PSU_SENSOR_TYPE17_BMR490},
{"INA226", PSU_SENSOR_TYPE27_INA226},
{"LTC2991", PSU_SENSOR_TYPE4_LTC2991},
{"LTC4287", PSU_SENSOR_TYPE9_LTC4287},
{"LX6301", PSU_SENSOR_TYPE14_LX6301},
{"MAX20826", PSU_SENSOR_TYPE19_MAX20826},
{"MAX34451", PSU_SENSOR_TYPE21_MAX34451},
{"MP2925", PSU_SENSOR_TYPE24_MP2925},
{"MP2929", PSU_SENSOR_TYPE25_MP2929},
{"MP5998", PSU_SENSOR_TYPE26_MP5998},
{"Q50SN12072", PSU_SENSOR_TYPE10_Q50SN12072},
{"Q54SN120A4", PSU_SENSOR_TYPE11_Q54SN120A4},
{"RAA228228", PSU_SENSOR_TYPE5_RAA228228},
{"RAA229141", PSU_SENSOR_TYPE20_RAA229141},
{"RAA229621", PSU_SENSOR_TYPE28_RAA229621},
{"RAA229639", PSU_SENSOR_TYPE13_RAA229639},
{"SBRMI_1_0", PSU_SENSOR_TYPE22_SBRMI_I3C},
{"SBRMI_2_0", PSU_SENSOR_TYPE22_SBRMI_I3C},
{"SBTSI_1_0", PSU_SENSOR_TYPE15_SBTSI_I3C},
{"SBTSI_1_1", PSU_SENSOR_TYPE15_SBTSI_I3C},
{"SBTSI_2_0", PSU_SENSOR_TYPE15_SBTSI_I3C},
{"SBTSI_2_1", PSU_SENSOR_TYPE15_SBTSI_I3C},
{"SBTSI_I3C", PSU_SENSOR_TYPE15_SBTSI_I3C},
{"TDA38725", PSU_SENSOR_TYPE6_TDA38725},
{"TDA38740", PSU_SENSOR_TYPE7_TDA38740},
{"TPS25990", PSU_SENSOR_TYPE12_TPS25990},
{"TPS546E25", PSU_SENSOR_TYPE16_TPS546E25},
{"XDPE19284C", PSU_SENSOR_TYPE18_XDPE19284C},
{"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();
}
}
}
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();
}
}
}
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();
}
}
}
for (auto& config : data.virtual_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();
}
}
}
for (auto& config : data.gpio_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();
}
}
}
for (auto& config : data.adc_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;
}
bool EntityConfigJsonImpl::IsRedfishRouteConfig(std::string_view type) {
return type == "RedfishRoute";
}
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;
}
bool EntityConfigJsonImpl::IsConfigKeyOwningAllSensors(
absl::string_view config_key) const {
return data_store_.immutable_data.redfish_route_configs
.configs_owning_sensors_collection.contains(config_key);
}
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::IsGpioSensor(std::string_view type) {
return type == "GpioSensor";
}
bool EntityConfigJsonImpl::IsRedfishAggregatedBatch(std::string_view type) {
return type == "RedfishAggregatedTelemetry";
}
bool EntityConfigJsonImpl::IsRedfishAggregatedSensor(std::string_view type) {
return type == "RedfishAggregatedSensor";
}
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::IsAdcSensor(std::string_view type) {
return type == "AdcSensor";
}
bool EntityConfigJsonImpl::IsDimm(std::string_view type) {
return type == kDimmKeyword;
}
bool EntityConfigJsonImpl::IsManager(std::string_view type) {
return type == kManagerKeyword;
}
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_ptr = GetValueAsString(element, "Type");
if (type_ptr == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a string");
}
std::string type = *type_ptr;
// Replace all spaces with underscores in the type string.
absl::StrReplaceAll({{" ", "_"}}, &type);
// 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));
}
if (type == "CpuMetrics" || type == "IntelCpuMetrics") {
tlbmc_supported = true;
ECCLESIA_ASSIGN_OR_RETURN(
std::vector<SharedMemSensorConfig> cpu_metrics_configs,
ParseCpuMetricsConfig(element, config_name_with_index, is_detected));
data.shared_mem_sensor_configs.insert(
data.shared_mem_sensor_configs.end(),
std::make_move_iterator(cpu_metrics_configs.begin()),
std::make_move_iterator(cpu_metrics_configs.end()));
}
if (type == "DimmMetrics" || type == "IntelDimmMetrics") {
tlbmc_supported = true;
ECCLESIA_ASSIGN_OR_RETURN(
std::vector<SharedMemSensorConfig> dimm_metrics_configs,
ParseDimmMetricsConfig(element, config_name_with_index, is_detected));
data.shared_mem_sensor_configs.insert(
data.shared_mem_sensor_configs.end(),
std::make_move_iterator(dimm_metrics_configs.begin()),
std::make_move_iterator(dimm_metrics_configs.end()));
}
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 (IsGpioSensor(type)) {
tlbmc_supported = true;
absl::StatusOr<GpioSensorConfig> gpio_sensor_config =
ParseGpioSensorConfig(element, is_detected);
if (!gpio_sensor_config.ok()) {
return gpio_sensor_config.status();
}
gpio_sensor_config->mutable_entity_common_config()->set_board_config_key(
config_name_with_index);
data.gpio_sensor_configs.push_back(*gpio_sensor_config);
}
if (IsRedfishAggregatedBatch(type)) {
tlbmc_supported = true;
absl::StatusOr<RedfishAggregatedBatchConfig>
redfish_aggregated_batch_config =
ParseRedfishAggregatedBatchConfig(element, is_detected);
if (!redfish_aggregated_batch_config.ok()) {
return redfish_aggregated_batch_config.status();
}
data.redfish_aggregated_batch_configs.push_back(
*redfish_aggregated_batch_config);
}
if (IsRedfishAggregatedSensor(type)) {
tlbmc_supported = true;
absl::StatusOr<RedfishAggregatedSensorConfig>
redfish_aggregated_sensor_config =
ParseRedfishAggregatedSensorConfig(element, is_detected);
if (!redfish_aggregated_sensor_config.ok()) {
return redfish_aggregated_sensor_config.status();
}
redfish_aggregated_sensor_config->mutable_entity_common_config()
->set_board_config_key(config_name_with_index);
data.redfish_aggregated_sensor_configs.push_back(
*redfish_aggregated_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) || IsManager(type) ||
type == kComputerSystemKeyword) {
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 (IsAdcSensor(type)) {
tlbmc_supported = true;
ECCLESIA_ASSIGN_OR_RETURN(AdcSensorConfig adc_sensor_config,
ParseAdcSensorConfig(element, is_detected));
*adc_sensor_config.mutable_entity_common_config()
->mutable_board_config_key() = config_name_with_index;
data.adc_sensor_configs.push_back(std::move(adc_sensor_config));
}
if (IsRedfishRouteConfig(type)) {
tlbmc_supported = true;
const bool* owns_sensor_collection =
GetValueAsBool(element, "OwnsSensorsCollection");
if (owns_sensor_collection != nullptr && *owns_sensor_collection) {
data.redfish_route_configs.configs_owning_sensors_collection.insert(
std::string(config_name_with_index));
}
}
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 std::vector<RawFru>& raw_frus) {
EntityConfigJsonImplData new_data = ReloadConfig(
fru_table, enable_deterministic_bmc_ ? -1 : 0,
enable_deterministic_bmc_
? ReloadType::kReloadAll // reload all will not actually reload all
// data but only mutable. See next
// comment for details.
: ReloadType::kReloadFruAndTopologyOnly);
if (!new_data.mutable_data.parsed_status.ok()) {
LOG(WARNING) << "Failed to parse config: "
<< new_data.mutable_data.parsed_status;
}
// TODO(b/469890768): we are supposed to update "immutable data" as well for
// ondemand FRU rescan and update sensor collector with new data. Currently
// the refresh will only refresh mutable data for FRU modeling, the exposed
// sensor which in immutable data will not be updated, sensor collector will
// not be reconfigured with new data.
// 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) {
for (const auto& raw_fru : raw_frus) {
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()) {
if (enable_deterministic_bmc_ && !raw_fru.present()) {
// TODO(haoooamazing): Add logic to model absent but expected FRU instead
// of skipping.
continue;
}
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,
enable_deterministic_bmc_, offline_node_entities_);
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};
}
const bool* compound_frus_ptr =
GetValueAsBool(config_data.config, "CompoundFru");
bool is_compound_fru = compound_frus_ptr != nullptr && *compound_frus_ptr;
std::vector<std::vector<FruKey>> fru_groups;
if (is_compound_fru) {
if (!config_data.fru_keys.empty()) {
absl::flat_hash_map<
std::tuple<std::string, std::string, std::string, std::string>,
std::vector<FruKey>>
pn_sn_groups;
for (const auto& fru_key : config_data.fru_keys) {
const Fru& fru =
mutable_data.fru_table.key_to_fru().at(fru_key.ToString());
auto key =
std::make_tuple(fru.data().fru_info().board_part_number(),
fru.data().fru_info().board_serial_number(),
fru.data().fru_info().product_part_number(),
fru.data().fru_info().product_serial_number());
pn_sn_groups[key].push_back(fru_key);
}
for (auto const& [key, frus] : pn_sn_groups) {
fru_groups.push_back(frus);
}
}
} else {
for (const auto& fru_key : config_data.fru_keys) {
fru_groups.push_back({fru_key});
}
}
// 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 < fru_groups.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);
if (!fru_groups[i].empty() &&
absl::StrContainsIgnoreCase(config_name_with_index, "$bus")) {
absl::StrReplaceAll({{"_", " "}}, &config_name_with_index);
absl::StatusOr<std::string> bus_value_substitute_result =
SubstituteExpressionVariables(config_name_with_index, "$bus",
fru_groups[i][0].bus);
if (bus_value_substitute_result.ok()) {
config_name_with_index = bus_value_substitute_result.value();
}
absl::StrReplaceAll({{" ", "_"}}, &config_name_with_index);
}
topology_config_node.set_name(config_name_with_index);
for (size_t j = 0; j < fru_groups[i].size(); j++) {
std::string fru_key = fru_groups[i][j].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) {
const Fru& parent_fru =
mutable_data.fru_table.key_to_fru().at(fru_key);
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);
*sub_fru_info.mutable_i2c_info() = parent_fru.i2c_info();
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));
const bool* is_replaceable =
GetValueAsBool(config_data.config, "Replaceable");
if (is_replaceable != nullptr) {
fru.mutable_attributes()
->mutable_chassis_properties()
->set_replaceable(*is_replaceable);
}
}
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 =
SubstituteExpressionVariables(config_bus_view, "$bus",
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 =
SubstituteExpressionVariables(field_value_str_copy, "$bus",
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 =
SubstituteExpressionVariables(field_value_str_copy, "$index",
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 &&
(!is_compound_fru || j == 0)) {
// For Compound FRU objects, we only parse the sensor config for the
// first EEPROM matched, since the subsequent elements will be
// duplicates of the first. j == 0 is the first EEPROM.
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_power_port_config_status =
ParsePowerPortConfig(element, topology_config_node);
if (!parse_power_port_config_status.ok()) {
mutable_data.parsed_status = parse_power_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};
}
absl::Status parse_manager_status =
ParseManagerConfig(mutable_data, element, topology_config_node);
if (!parse_manager_status.ok()) {
mutable_data.parsed_status = parse_manager_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_system_status =
ParseSystemConfig(mutable_data, element, topology_config_node);
if (!parse_system_status.ok()) {
mutable_data.parsed_status = parse_system_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,
fru_info.i2c_info().bus());
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};
}
absl::Status parse_power_supply_info_status =
ParsePowerSupplyConfig(config_data.config, fru);
if (!parse_power_supply_info_status.ok()) {
mutable_data.parsed_status = parse_power_supply_info_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
topology_config_node.mutable_fru_info()->set_fru_key(fru_key);
if (is_subfru) {
mutable_data.topology_config.mutable_fru_configs()->insert(
{fru_key, config_name_with_index});
topology_config_node.mutable_fru_info()->set_is_sub_fru(true);
for (const auto& key_in_group : fru_groups[i]) {
fru_key_to_sub_fru_config[key_in_group.ToString()].insert(
config_name_with_index);
}
} else {
for (const auto& key_in_group : fru_groups[i]) {
mutable_data.topology_config.mutable_fru_configs()->insert(
{key_in_group.ToString(), config_name_with_index});
}
}
// tlBMC own cables by config.
if ((!GetTlbmcConfig().fru_collector_module().own_cables_in_redfish() &&
fru_info.attributes().resource_type() == RESOURCE_TYPE_CABLE)) {
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 (GetTlbmcConfig()
.fru_collector_module()
.no_associations_based_topology() ||
!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, "Units");
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);
std::optional<uint64_t> index =
GetValueAsUintFromStringOrInteger(threshold_element, "Index");
if (!index.has_value()) {
for (const auto& label : labels) {
*label_to_threshold_configs[label].add_threshold_configs() =
threshold_config;
}
} else {
std::string label = absl::Substitute("temp$0", *index);
if (std::find(labels.begin(), labels.end(), label) == labels.end()) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid threshold config: Index doesn't have matching Name",
" config is ", threshold_element.dump()));
}
*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) {
if (const auto* refresh_interval =
GetValueAsUint(config, kRefreshIntervalKeyword);
refresh_interval != nullptr) {
ECCLESIA_ASSIGN_OR_RETURN(google::protobuf::Duration proto_duration,
ecclesia::AbslDurationToProtoDuration(
absl::Milliseconds(*refresh_interval)));
*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);
}
if (const std::string* power_state = GetValueAsString(config, "PowerState");
power_state != nullptr) {
if (*power_state == "On" || *power_state == "BiosPost") {
if (*power_state == "On") {
sensor.mutable_entity_common_config()->set_power_state(
SENSOR_POWER_REQUIREMENT_CHASSIS_ON);
} else {
sensor.mutable_entity_common_config()->set_power_state(
SENSOR_POWER_REQUIREMENT_BIOS_POST);
}
const std::string* host_id = GetValueAsString(config, "HostId");
if (host_id == nullptr) {
LOG(ERROR) << "[DEBUG] Failed to find host id for sensor: "
<< sensor.entity_common_config().config_name();
} else {
std::string host_id_str = *host_id;
std::optional<uint64_t> bus =
GetValueAsUintFromStringOrInteger(config, "Bus");
if (bus.has_value() &&
absl::StrContainsIgnoreCase(host_id_str, "$bus")) {
absl::StatusOr<std::string> eval_result =
EvaluateStrExpressionSubstitution(
host_id_str,
absl::StrContains(host_id_str, "$bus") ? "$bus" : "$BUS",
absl::StrCat(*bus));
if (eval_result.ok()) {
host_id_str = *eval_result;
}
}
sensor.mutable_entity_common_config()->set_host_id(host_id_str);
}
} else {
sensor.mutable_entity_common_config()->set_power_state(
SENSOR_POWER_REQUIREMENT_ALWAYS);
}
}
return absl::OkStatus();
}
absl::flat_hash_set<absl::string_view> ParseRedfishHiddenSensors(
const nlohmann::json& config) {
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys;
const nlohmann::json::array_t* redfish_hidden =
GetValueAsArray(config, kRedfishHiddenKeyword);
if (redfish_hidden == nullptr) {
const bool* redfish_hidden_bool =
GetValueAsBool(config, kRedfishHiddenKeyword);
if (redfish_hidden_bool != nullptr && *redfish_hidden_bool) {
redfish_hidden_sensor_keys.insert(kAllKeyword);
}
return redfish_hidden_sensor_keys;
}
for (const auto& redfish_hidden_sensor_key : *redfish_hidden) {
const std::string* redfish_hidden_sensor_key_str =
redfish_hidden_sensor_key.get_ptr<const std::string*>();
if (redfish_hidden_sensor_key_str != nullptr) {
redfish_hidden_sensor_keys.insert(*redfish_hidden_sensor_key_str);
}
}
return redfish_hidden_sensor_keys;
}
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::optional<RelatedItem> related_item = ParseRelatedItem(config);
absl::flat_hash_map<std::string, std::string> label_to_name;
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);
label_to_name[label] = *name_str;
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);
}
ECCLESIA_ASSIGN_OR_RETURN(label_to_name, ParseLabelToName(config, labels));
}
absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs;
ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs,
ParseThresholdConfigsForHwmonTemp(config, labels));
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
for (const auto& label : labels) {
SensorInstanceProperties sensor_instance_properties;
if (label_to_name.contains(label)) {
std::string_view name = label_to_name[label];
sensor_instance_properties.set_name(name);
if (redfish_hidden_sensor_keys.contains(name) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
sensor_instance_properties.set_redfish_hidden(true);
}
}
if (label_to_threshold_configs.contains(label)) {
*sensor_instance_properties.mutable_thresholds() =
label_to_threshold_configs[label];
}
*sensor_instance_properties.mutable_reading_ranges() =
reading_range_configs;
hwmon_temp_sensor_config.mutable_label_to_sensor_instance_properties()
->insert({label, sensor_instance_properties});
}
const std::string* sensor_group =
GetValueAsString(config, kSensorGroupKeyword);
if (sensor_group != nullptr) {
hwmon_temp_sensor_config.mutable_entity_common_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.mutable_entity_common_config()->set_config_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));
absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs;
ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs,
ParseThresholdConfigs(config));
absl::flat_hash_map<std::string, ReadingRangeConfigs> label_to_reading_range =
ParseReadingRangeConfigs(config, labels);
absl::flat_hash_map<std::string, ReadingTransformConfig>
label_to_reading_transform = ParseReadingTransformConfigs(config, labels);
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
for (const auto& label : labels) {
SensorInstanceProperties sensor_instance_properties;
if (label_to_name.contains(label)) {
std::string_view name = label_to_name[label];
sensor_instance_properties.set_name(name);
if (redfish_hidden_sensor_keys.contains(name) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
sensor_instance_properties.set_redfish_hidden(true);
}
}
if (label_to_threshold_configs.contains(label)) {
*sensor_instance_properties.mutable_thresholds() =
label_to_threshold_configs[label];
}
if (label_to_reading_range.contains(label)) {
*sensor_instance_properties.mutable_reading_ranges() =
label_to_reading_range[label];
}
if (label_to_reading_transform.contains(label)) {
*sensor_instance_properties.mutable_reading_transform_config() =
label_to_reading_transform[label];
}
if (related_item.has_value()) {
*sensor_instance_properties.mutable_related_item() = *related_item;
}
psu_sensor_config.mutable_label_to_sensor_instance_properties()->insert(
{label, sensor_instance_properties});
}
const std::string* sensor_group =
GetValueAsString(config, kSensorGroupKeyword);
if (sensor_group != nullptr) {
psu_sensor_config.mutable_entity_common_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_instance_properties()
->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_instance_properties()
->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.mutable_entity_common_config()->set_config_name(*name);
fan_pwm_config.mutable_instance_properties()->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_instance_properties()->mutable_related_item() =
*related_item;
}
const std::string* sensor_group =
GetValueAsString(config, kSensorGroupKeyword);
if (sensor_group != nullptr) {
fan_pwm_config.mutable_entity_common_config()->set_sensor_group(
*sensor_group);
}
fan_pwm_config.mutable_entity_common_config()->set_config_detected(
is_detected);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
if (redfish_hidden_sensor_keys.contains(
fan_pwm_config.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
fan_pwm_config.mutable_instance_properties()->set_redfish_hidden(true);
}
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_instance_properties()->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.mutable_entity_common_config()->set_config_name(*name);
fan_tach_config.mutable_instance_properties()->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_instance_properties()->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_instance_properties()->mutable_related_item() =
*related_item;
}
const std::string* sensor_group =
GetValueAsString(config, kSensorGroupKeyword);
if (sensor_group != nullptr) {
fan_tach_config.mutable_entity_common_config()->set_sensor_group(
*sensor_group);
}
fan_tach_config.mutable_entity_common_config()->set_config_detected(
is_detected);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
if (redfish_hidden_sensor_keys.contains(
fan_tach_config.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
fan_tach_config.mutable_instance_properties()->set_redfish_hidden(true);
}
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.mutable_instance_properties()->set_name(*name);
shared_mem_sensor_config.mutable_instance_properties()->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_instance_properties()
->mutable_thresholds() = label_to_threshold_configs[""];
ECCLESIA_RETURN_IF_ERROR(
ParseCommonEntityConfig(config, shared_mem_sensor_config));
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
if (related_item.has_value()) {
*shared_mem_sensor_config.mutable_instance_properties()
->mutable_related_item() = *related_item;
}
shared_mem_sensor_config.mutable_entity_common_config()->set_config_detected(
is_detected);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
if (redfish_hidden_sensor_keys.contains(
shared_mem_sensor_config.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
shared_mem_sensor_config.mutable_instance_properties()->set_redfish_hidden(
true);
}
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.mutable_entity_common_config()->set_config_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));
absl::flat_hash_map<std::string, RelatedItem> name_to_related_item;
ECCLESIA_ASSIGN_OR_RETURN(name_to_related_item, ParseRelatedItemMap(config));
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
for (const auto& label : labels) {
SensorInstanceProperties properties;
std::string_view name = label_to_name[label];
properties.set_name(name);
auto it = name_to_related_item.find(name);
if (it != name_to_related_item.end()) {
*properties.mutable_related_item() = it->second;
}
if (redfish_hidden_sensor_keys.contains(name) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
properties.set_redfish_hidden(true);
}
intel_cpu_sensor_config.mutable_label_to_sensor_instance_properties()
->insert({label, properties});
}
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.mutable_entity_common_config()->set_sensor_group(
*sensor_group);
}
return intel_cpu_sensor_config;
}
static absl::StatusOr<std::vector<std::string>> GetSystemNames(
const nlohmann::json& config) {
std::vector<std::string> names;
const uint64_t* systems_num = GetValueAsUint(config, "Systems");
if (systems_num != nullptr && *systems_num > 0) {
for (uint64_t i = 1; i <= *systems_num; ++i) {
names.push_back(absl::StrCat("system", i));
}
} else {
names.push_back("");
}
return names;
}
/*
Expected JSON structure for DimmMetrics:
{
// Required: Number of DIMMs per system.
"Dimms": 16,
// Optional: Number of system partitions (default 1).
"Systems": 1,
// Required: List of metrics to collect.
"Metrics": ["metric1", "metric2"],
// Required.
"TlbmcOwned": true
}
*/
absl::StatusOr<std::vector<SharedMemSensorConfig>>
EntityConfigJsonImpl::ParseDimmMetricsConfig(
const nlohmann::json& config, absl::string_view config_name_with_index,
bool is_detected) {
std::vector<SharedMemSensorConfig> configs;
const uint64_t* dimms_ptr = GetValueAsUint(config, "Dimms");
if (dimms_ptr == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Dimms field is not found");
}
uint64_t dimms = *dimms_ptr;
if (dimms > 256) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Dimms field is too large: ", dimms));
}
const nlohmann::json::array_t* metrics = GetValueAsArray(config, "Metrics");
if (metrics == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Metrics field is not an array");
}
ECCLESIA_ASSIGN_OR_RETURN(std::vector<std::string> system_names,
GetSystemNames(config));
for (size_t system_index = 0; system_index < system_names.size();
++system_index) {
const auto& system_name = system_names[system_index];
for (size_t dimm = 0; dimm < dimms; ++dimm) {
size_t absolute_dimm = system_index * dimms + dimm;
for (const auto& metric_json : *metrics) {
const std::string* metric_ptr =
metric_json.get_ptr<const std::string*>();
if (metric_ptr == nullptr) {
LOG(ERROR) << "Invalid config: Metric field is not a string";
continue;
}
std::string metric = *metric_ptr;
if (absl::StrContains(metric, ' ')) {
LOG(ERROR) << "Invalid config: Metric name contains spaces: "
<< metric;
continue;
}
std::string name =
absl::StrCat(system_name, system_name.empty() ? "" : "_", "dimm",
dimm, "_", metric);
SharedMemSensorConfig shared_mem_sensor_config;
shared_mem_sensor_config.set_type(
SHARED_MEM_SENSOR_TYPE_TYPE1_NUMERIC_SENSOR);
shared_mem_sensor_config.mutable_instance_properties()->set_name(name);
shared_mem_sensor_config.mutable_instance_properties()->set_unit(
SensorUnit::UNIT_COUNT);
const bool* redfish_hidden =
GetValueAsBool(config, kRedfishHiddenKeyword);
if (redfish_hidden != nullptr) {
shared_mem_sensor_config.mutable_instance_properties()
->set_redfish_hidden(*redfish_hidden);
}
RelatedItem related_item;
related_item.set_id(absl::StrCat("dimm", absolute_dimm));
related_item.set_type(RESOURCE_TYPE_DIMM);
if (!system_name.empty()) {
related_item.set_system_id(system_name);
}
*shared_mem_sensor_config.mutable_instance_properties()
->mutable_related_item() = related_item;
shared_mem_sensor_config.mutable_entity_common_config()
->set_config_detected(is_detected);
shared_mem_sensor_config.mutable_entity_common_config()
->set_board_config_key(config_name_with_index);
ECCLESIA_RETURN_IF_ERROR(
ParseCommonEntityConfig(config, shared_mem_sensor_config));
configs.push_back(std::move(shared_mem_sensor_config));
}
}
}
return configs;
}
absl::StatusOr<GpioSensorConfig> EntityConfigJsonImpl::ParseGpioSensorConfig(
const nlohmann::json& config, bool is_detected) {
GpioSensorConfig gpio_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");
}
gpio_sensor_config.mutable_entity_common_config()->set_config_name(*name);
gpio_sensor_config.mutable_instance_properties()->set_name(*name);
const std::string* gpio_pin_name = GetValueAsString(config, "GpioPinName");
if (gpio_pin_name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: GpioPinName field is not a string or missing");
}
gpio_sensor_config.set_gpio_pin_name(*gpio_pin_name);
ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, gpio_sensor_config));
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
if (related_item.has_value()) {
*gpio_sensor_config.mutable_instance_properties()->mutable_related_item() =
*related_item;
}
constexpr int kDefaultMaxGpioReading = 1;
constexpr int kDefaultMinGpioReading = 0;
ReadingRangeConfigs reading_range_configs = ParseReadingRangeConfigs(
config, kDefaultMinGpioReading, kDefaultMaxGpioReading);
*gpio_sensor_config.mutable_instance_properties()->mutable_reading_ranges() =
reading_range_configs;
gpio_sensor_config.mutable_instance_properties()->set_unit(
ParseSensorUnit(config));
gpio_sensor_config.mutable_entity_common_config()->set_config_detected(
is_detected);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
if (redfish_hidden_sensor_keys.contains(
gpio_sensor_config.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
gpio_sensor_config.mutable_instance_properties()->set_redfish_hidden(true);
}
return gpio_sensor_config;
}
absl::StatusOr<RedfishAggregatedBatchConfig>
EntityConfigJsonImpl::ParseRedfishAggregatedBatchConfig(
const nlohmann::json& config, bool is_detected) {
RedfishAggregatedBatchConfig redfish_aggregated_batch_config;
const std::string* name = GetValueAsString(config, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
redfish_aggregated_batch_config.set_name(*name);
const std::string* source = GetValueAsString(config, "Source");
if (source == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Source field is not a string or missing");
}
redfish_aggregated_batch_config.set_source(*source);
const std::string* redfish_location =
GetValueAsString(config, "RedfishLocation");
if (redfish_location == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: RedfishLocation field is not a string or missing");
}
redfish_aggregated_batch_config.set_redfish_location(*redfish_location);
const nlohmann::json::array_t* telemetry_configs =
GetValueAsArray(config, "Telemetry");
if (telemetry_configs == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Telemetry field is not a list or missing: ",
config.dump()));
}
for (const auto& telemetry_config : *telemetry_configs) {
RedfishAggregatedSensorConfig telemetry;
const std::string* name = GetValueAsString(telemetry_config, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
telemetry.mutable_entity_common_config()->set_config_name(*name);
telemetry.mutable_instance_properties()->set_name(*name);
const std::string* property =
GetValueAsString(telemetry_config, "Property");
if (property == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Property field is not a string or missing");
}
telemetry.set_property(*property);
const std::string* devpath = GetValueAsString(telemetry_config, "Devpath");
if (devpath == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Devpath field is not a string or missing");
}
telemetry.set_devpath(*devpath);
const std::string* telemetry_type =
GetValueAsString(telemetry_config, "TelemetryType");
if (telemetry_type == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: TelemetryType field is not a string or missing");
}
telemetry.set_sensor_type(*telemetry_type);
const std::string* telemetry_units =
GetValueAsString(telemetry_config, "TelemetryUnits");
if (telemetry_units == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: TelemetryUnits field is not a string or missing");
}
telemetry.set_sensor_units(*telemetry_units);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(telemetry_config);
if (redfish_hidden_sensor_keys.contains(
telemetry.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
telemetry.mutable_instance_properties()->set_redfish_hidden(true);
}
*redfish_aggregated_batch_config.add_telemetry_configs() =
std::move(telemetry);
}
return redfish_aggregated_batch_config;
}
absl::StatusOr<RedfishAggregatedSensorConfig>
EntityConfigJsonImpl::ParseRedfishAggregatedSensorConfig(
const nlohmann::json& config, bool is_detected) {
RedfishAggregatedSensorConfig redfish_aggregated_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");
}
redfish_aggregated_sensor_config.mutable_entity_common_config()
->set_config_name(*name);
redfish_aggregated_sensor_config.mutable_instance_properties()->set_name(
*name);
ECCLESIA_RETURN_IF_ERROR(
ParseCommonEntityConfig(config, redfish_aggregated_sensor_config));
const std::string* source = GetValueAsString(config, "Source");
if (source == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Source field is not a string or missing");
}
redfish_aggregated_sensor_config.set_source(*source);
const std::string* sensor_type = GetValueAsString(config, "ReadingType");
if (sensor_type == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: ReadingType field is not a string or missing");
}
redfish_aggregated_sensor_config.set_sensor_type(*sensor_type);
const std::string* sensor_units = GetValueAsString(config, "ReadingUnits");
if (sensor_units == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: ReadingUnits field is not a string or missing");
}
redfish_aggregated_sensor_config.set_sensor_units(*sensor_units);
const std::string* aggregation_prefix =
GetValueAsString(config, "AggregationPrefix");
if (aggregation_prefix == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: AggregationPrefix field is not a string or missing");
}
redfish_aggregated_sensor_config.set_aggregation_prefix(*aggregation_prefix);
if (const auto* property = GetValueAsString(config, "Property");
property != nullptr) {
redfish_aggregated_sensor_config.set_property(*property);
}
if (const auto* devpath = GetValueAsString(config, "Devpath");
devpath != nullptr) {
redfish_aggregated_sensor_config.set_devpath(*devpath);
}
redfish_aggregated_sensor_config.mutable_entity_common_config()
->set_config_detected(is_detected);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
if (redfish_hidden_sensor_keys.contains(
redfish_aggregated_sensor_config.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
redfish_aggregated_sensor_config.mutable_instance_properties()
->set_redfish_hidden(true);
}
return redfish_aggregated_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.mutable_instance_properties()->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_instance_properties()->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_instance_properties()
->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.mutable_entity_common_config()->set_config_name(*name);
virtual_sensor_config.mutable_instance_properties()->set_name(*name);
absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
if (redfish_hidden_sensor_keys.contains(
virtual_sensor_config.instance_properties().name()) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
virtual_sensor_config.mutable_instance_properties()->set_redfish_hidden(
true);
}
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)});
(*mutable_data.fru_table.mutable_pwm_sensor_to_fan_group())[*pwm_sensor_name]
.add_fan_keys(fan_node_name);
return absl::OkStatus();
}
absl::Status EntityConfigJsonImpl::ParseManagerConfig(
EntityConfigJsonImplMutableData& mutable_data, const nlohmann::json& config,
TopologyConfigNode& topology_config_node) {
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 != kManagerKeyword) {
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 manager "
"config: ",
config.dump()));
}
const nlohmann::json::array_t* manager_for_servers =
GetValueAsArray(config, "ManagerForServers");
if (manager_for_servers == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: ManagerForServers field is not found for manager "
"config: ",
config.dump()));
}
const std::string* manager_for_chassis =
GetValueAsString(config, "ManagerForChassis");
if (manager_for_chassis == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: ManagerForChassis field is not found for manager "
"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 manager "
"config: ",
config.dump()));
}
// Add manager to fru table.
Fru manager;
manager.mutable_attributes()->set_resource_type(RESOURCE_TYPE_MANAGER);
manager.mutable_attributes()->set_key(*name);
manager.mutable_attributes()->set_status(Status::STATUS_READY);
manager.mutable_attributes()->mutable_refresh_policy()->set_refresh_mode(
RefreshMode::REFRESH_MODE_ON_DEMAND);
manager.mutable_data()->mutable_manager_info()->set_name(*name);
manager.mutable_data()->mutable_manager_info()->set_manager_in_chassis(
topology_config_node.name());
for (const auto& manager_for_server : *manager_for_servers) {
manager.mutable_data()->mutable_manager_info()->add_manager_for_servers(
manager_for_server.get<std::string>());
}
manager.mutable_data()->mutable_manager_info()->set_manager_for_chassis(
*manager_for_chassis);
// Topologically this needs to be a child of the current chassis
TopologyConfigNode manager_node;
manager_node.set_name(*name);
manager_node.mutable_fru_info()->set_fru_key(*name);
manager_node.set_config_key(*name);
// Assume manager is embedded in the chassis
manager_node.mutable_location_context()->set_location_type(
PART_LOCATION_TYPE_EMBEDDED);
// Add topology port configs for upstream and downstream
PortConfig chassis_to_manager_port_config;
chassis_to_manager_port_config.set_port_type(PORT_TYPE_DOWNSTREAM);
chassis_to_manager_port_config.set_port_name(*name);
chassis_to_manager_port_config.set_port_label(*service_label);
topology_config_node.mutable_port_configs()->insert(
{*name, std::move(chassis_to_manager_port_config)});
PortConfig manager_to_chassis_port_config;
manager_to_chassis_port_config.set_port_type(PORT_TYPE_UPSTREAM);
manager_to_chassis_port_config.set_port_name(*name);
*manager_node.add_upstream_port_configs() =
std::move(manager_to_chassis_port_config);
// The config name and the fru key are identical.
mutable_data.topology_config.mutable_topology_config_nodes()->insert(
{*name, std::move(manager_node)});
mutable_data.topology_config.mutable_fru_configs()->insert({*name, *name});
mutable_data.fru_table.mutable_key_to_fru()->insert(
{*name, std::move(manager)});
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()));
}
const absl::flat_hash_set<absl::string_view> redfish_hidden_sensor_keys =
ParseRedfishHiddenSensors(config);
std::vector<nic_veeprom::NicTelemetryName> telemetry_names;
ECCLESIA_ASSIGN_OR_RETURN(telemetry_names, ParseNicTelemetryNames(config));
for (const auto& telemetry_name : telemetry_names) {
NicTelemetryInstance* telemetry_instance =
nic_telemetry_config.add_telemetry_instances();
telemetry_instance->set_telemetry_name(telemetry_name);
if (redfish_hidden_sensor_keys.contains(
nic_veeprom::NicTelemetryName_Name(telemetry_name)) ||
redfish_hidden_sensor_keys.contains(kAllKeyword)) {
telemetry_instance->mutable_instance_properties()->set_redfish_hidden(
true);
}
}
nic_telemetry_config.mutable_entity_common_config()->set_config_detected(
is_detected);
return nic_telemetry_config;
}
absl::StatusOr<AdcSensorConfig> EntityConfigJsonImpl::ParseAdcSensorConfig(
const nlohmann::json& config, bool is_detected) {
AdcSensorConfig adc_sensor_config;
const std::string* name = GetValueAsString(config, "Name");
bool has_bus = config.contains("Bus");
bool has_address = config.contains("Address");
if (has_bus != has_address) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Bus and Address must both be present: ",
config.dump()));
}
bool has_i2c = has_bus && has_address;
if (name == nullptr && !has_i2c) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: element must have either Name (iio) or "
"Bus and Address (i2c) fields: ",
config.dump()));
}
if (name != nullptr) {
adc_sensor_config.set_device_name(*name);
}
if (has_i2c) {
ECCLESIA_ASSIGN_OR_RETURN(*adc_sensor_config.mutable_hal_common_config(),
ParseHalCommonConfig(config));
}
const nlohmann::json::array_t* channel_configs =
GetValueAsArray(config, "Channels");
if (channel_configs == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: element does not have a Channels field: ",
config.dump()));
}
std::vector<AdcChannel> adc_channel_configs_store;
for (const auto& channel_config : *channel_configs) {
AdcChannel adc_channel_config;
const std::string* sensor_name =
GetValueAsString(channel_config, "SensorName");
if (sensor_name == nullptr) {
LOG(WARNING) << "SensorName is missing, skipping the Channel.";
continue;
}
adc_channel_config.mutable_instance_properties()->set_name(*sensor_name);
const std::string* file_name = GetValueAsString(channel_config, "FileName");
if (file_name == nullptr) {
LOG(WARNING) << "FileName is missing, skipping the Channel.";
continue;
}
adc_channel_config.set_file_name(*file_name);
adc_channel_config.mutable_instance_properties()->set_unit(
ParseSensorUnit(channel_config));
if (adc_channel_config.mutable_instance_properties()->unit() ==
SensorUnit::UNIT_UNKNOWN) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Unit is required for ADC sensors: ", config.dump()));
}
const auto* gpio_threshold_millivolts =
GetValueAsInt(channel_config, "ThresholdMillivolts");
// Threshold is required for ADC virtual GPIO sensors.
if (gpio_threshold_millivolts == nullptr &&
adc_channel_config.mutable_instance_properties()->unit() ==
SensorUnit::UNIT_BINARY) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Threshold is required for ADC "
"virtual GPIO sensors: ",
config.dump()));
}
if (gpio_threshold_millivolts != nullptr) {
adc_channel_config.set_gpio_threshold_millivolts(
*gpio_threshold_millivolts);
}
std::optional<double> scale =
GetValueAsDoubleFromFloatOrInteger(channel_config, "Scale");
if (scale.has_value()) {
adc_channel_config.mutable_instance_properties()
->mutable_reading_transform_config()
->set_scale(*scale);
}
std::optional<RelatedItem> related_item = ParseRelatedItem(channel_config);
if (related_item.has_value()) {
*adc_channel_config.mutable_instance_properties()
->mutable_related_item() = *related_item;
}
adc_channel_configs_store.push_back(adc_channel_config);
}
adc_sensor_config.mutable_channels()->Add(adc_channel_configs_store.begin(),
adc_channel_configs_store.end());
// If IntervalMs is not set, use the default refresh interval.
constexpr uint64_t kDefaultAdcSensorRefreshInterval = 5000;
std::optional<uint64_t> interval_in_ms =
GetValueAsUintFromStringOrInteger(config, "IntervalMs");
if (!interval_in_ms.has_value()) {
LOG(INFO) << "IntervalMs is not set for ADC sensor: "
<< (name != nullptr ? absl::string_view(*name)
: absl::string_view())
<< ". Using default refresh interval: "
<< kDefaultAdcSensorRefreshInterval;
interval_in_ms = kDefaultAdcSensorRefreshInterval;
}
auto proto_duration = ecclesia::AbslDurationToProtoDuration(
absl::Milliseconds(*interval_in_ms));
if (!proto_duration.ok()) {
return proto_duration.status();
}
*adc_sensor_config.mutable_entity_common_config()
->mutable_refresh_interval() = *proto_duration;
adc_sensor_config.mutable_entity_common_config()->set_config_detected(
is_detected);
return adc_sensor_config;
}
/*
Expected JSON structure for CpuMetrics:
{
// Required: Number of processors. Processors are zero indexed and named cpu0,
// cpu1, ...
"Cpus": 2,
// Optional: Number of system partitions (default 1; ie `system`).
"Systems": 1,
// Optional: Number of cores per CPU. If 0 (default), only CPU-level metrics
// are generated.
"Cores": 0,
// Optional: Threads per core. Must be 0 if Cores is 0.
"ThreadsPerCore": 0,
// Required: List of metrics to collect.
"Metrics": ["metric1", "metric2"],
// Required.
"TlbmcOwned": true
// Optional: If true, the sensors will be hidden from the Redfish interface.
"RedfishHidden": true
}
*/
absl::StatusOr<std::vector<SharedMemSensorConfig>>
EntityConfigJsonImpl::ParseCpuMetricsConfig(
const nlohmann::json& config, absl::string_view config_name_with_index,
bool is_detected) {
std::vector<SharedMemSensorConfig> configs;
const uint64_t* cpus_ptr = GetValueAsUint(config, "Cpus");
if (cpus_ptr == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Cpus field must be an integer");
}
std::vector<std::string> cpu_names;
cpu_names.reserve(*cpus_ptr);
for (uint64_t i = 0; i < *cpus_ptr; ++i) {
cpu_names.push_back(absl::StrCat("cpu", i));
}
uint64_t cores = 0;
const uint64_t* cores_ptr = GetValueAsUint(config, "Cores");
if (cores_ptr != nullptr) {
cores = *cores_ptr;
}
if (cores > 512) {
return absl::InvalidArgumentError(
"Invalid config: Cores must be between 0 and 512");
}
uint64_t threads = 0;
const uint64_t* threads_ptr = GetValueAsUint(config, "ThreadsPerCore");
if (threads_ptr != nullptr) {
threads = *threads_ptr;
}
if (threads > 4) {
return absl::InvalidArgumentError(
"Invalid config: ThreadsPerCore must be between 0 and 4");
}
if (cores == 0 && threads > 0) {
return absl::InvalidArgumentError(
"Invalid config: Cores cannot be 0 if ThreadsPerCore is greater than "
"0");
}
const nlohmann::json::array_t* metrics = GetValueAsArray(config, "Metrics");
if (metrics == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Metrics field is not an array");
}
for (const auto& metric_json : *metrics) {
const std::string* metric_ptr = metric_json.get_ptr<const std::string*>();
if (metric_ptr != nullptr && absl::StrContains(*metric_ptr, ' ')) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Metric name contains space: ", *metric_ptr));
}
}
ECCLESIA_ASSIGN_OR_RETURN(std::vector<std::string> system_names,
GetSystemNames(config));
const bool* redfish_hidden_ptr =
GetValueAsBool(config, kRedfishHiddenKeyword);
bool is_hidden = (redfish_hidden_ptr != nullptr) && *redfish_hidden_ptr;
auto create_sensor = [&config, is_hidden, is_detected,
config_name_with_index](absl::string_view name,
absl::string_view cpu,
absl::string_view system_name)
-> absl::StatusOr<SharedMemSensorConfig> {
SharedMemSensorConfig shared_mem_sensor_config;
shared_mem_sensor_config.set_type(
SHARED_MEM_SENSOR_TYPE_TYPE1_NUMERIC_SENSOR);
shared_mem_sensor_config.mutable_instance_properties()->set_name(name);
shared_mem_sensor_config.mutable_instance_properties()->set_unit(
SensorUnit::UNIT_COUNT);
shared_mem_sensor_config.mutable_instance_properties()->set_redfish_hidden(
is_hidden);
RelatedItem related_item;
related_item.set_id(cpu);
related_item.set_type(RESOURCE_TYPE_PROCESSOR);
if (!system_name.empty()) {
related_item.set_system_id(system_name);
}
*shared_mem_sensor_config.mutable_instance_properties()
->mutable_related_item() = related_item;
shared_mem_sensor_config.mutable_entity_common_config()
->set_config_detected(is_detected);
shared_mem_sensor_config.mutable_entity_common_config()
->set_board_config_key(config_name_with_index);
ECCLESIA_RETURN_IF_ERROR(
ParseCommonEntityConfig(config, shared_mem_sensor_config));
return shared_mem_sensor_config;
};
for (const auto& system_name : system_names) {
for (const auto& cpu : cpu_names) {
if (cores == 0) {
for (const auto& metric_json : *metrics) {
const std::string* metric_ptr =
metric_json.get_ptr<const std::string*>();
if (metric_ptr == nullptr) {
continue;
}
std::string metric = *metric_ptr;
std::string name = absl::StrCat(
system_name, system_name.empty() ? "" : "_", cpu, "_", metric);
ECCLESIA_ASSIGN_OR_RETURN(
SharedMemSensorConfig shared_mem_sensor_config,
create_sensor(name, cpu, system_name));
configs.push_back(std::move(shared_mem_sensor_config));
}
} else {
for (size_t core = 0; core < cores; ++core) {
for (size_t thread = 0; thread < threads; ++thread) {
for (const auto& metric_json : *metrics) {
const std::string* metric_ptr =
metric_json.get_ptr<const std::string*>();
if (metric_ptr == nullptr) {
continue;
}
std::string metric = *metric_ptr;
std::string name =
absl::StrCat(system_name, system_name.empty() ? "" : "_", cpu,
"_core", core, "_thread", thread, "_", metric);
ECCLESIA_ASSIGN_OR_RETURN(
SharedMemSensorConfig shared_mem_sensor_config,
create_sensor(name, cpu, system_name));
configs.push_back(std::move(shared_mem_sensor_config));
}
}
}
for (const auto& metric_json : *metrics) {
const std::string* metric_ptr =
metric_json.get_ptr<const std::string*>();
if (metric_ptr == nullptr) {
continue;
}
std::string metric = *metric_ptr;
std::string name = absl::StrCat(
system_name, system_name.empty() ? "" : "_", cpu, "_", metric);
ECCLESIA_ASSIGN_OR_RETURN(
SharedMemSensorConfig shared_mem_sensor_config,
create_sensor(name, cpu, system_name));
configs.push_back(std::move(shared_mem_sensor_config));
}
}
}
}
return configs;
}
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::ParseSystemConfig(
EntityConfigJsonImplMutableData& mutable_data, const nlohmann::json& config,
TopologyConfigNode& topology_config_node) {
const bool* tlbmc_owned = GetValueAsBool(config, "TlbmcOwned");
if (tlbmc_owned == nullptr || !*tlbmc_owned) {
return absl::OkStatus();
}
const std::string* type_str = GetValueAsString(config, "Type");
if (type_str == nullptr || *type_str != kComputerSystemKeyword) {
return absl::OkStatus();
}
const std::string* system_name_ptr = GetValueAsString(config, "Name");
if (system_name_ptr == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Name field is not found for system "
"config: ",
config.dump()));
}
const std::string system_name = *system_name_ptr;
// Add system to fru table.
Fru system_fru;
system_fru.mutable_attributes()->set_resource_type(
RESOURCE_TYPE_COMPUTER_SYSTEM);
system_fru.mutable_attributes()->set_key(system_name);
system_fru.mutable_attributes()->set_status(Status::STATUS_READY);
system_fru.mutable_attributes()->mutable_refresh_policy()->set_refresh_mode(
RefreshMode::REFRESH_MODE_ON_DEMAND);
mutable_data.fru_table.mutable_key_to_fru()->insert(
{system_name, std::move(system_fru)});
TopologyConfigNode system_node;
system_node.set_name(system_name);
system_node.mutable_fru_info()->set_fru_key(system_name);
system_node.set_config_key(system_name);
// Add topology port configs for upstream and downstream
PortConfig chassis_to_system_port_config;
chassis_to_system_port_config.set_port_type(PORT_TYPE_DOWNSTREAM);
chassis_to_system_port_config.set_port_name(system_name);
chassis_to_system_port_config.set_port_label(system_name);
topology_config_node.mutable_port_configs()->insert(
{system_name, std::move(chassis_to_system_port_config)});
PortConfig system_to_chassis_port_config;
system_to_chassis_port_config.set_port_type(PORT_TYPE_UPSTREAM);
system_to_chassis_port_config.set_port_name(system_name);
*system_node.add_upstream_port_configs() =
std::move(system_to_chassis_port_config);
mutable_data.topology_config.mutable_topology_config_nodes()->insert(
{system_name, std::move(system_node)});
mutable_data.topology_config.mutable_fru_configs()->insert(
{system_name, system_name});
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()});
} else if (downstream_fru.attributes().resource_type() ==
RESOURCE_TYPE_COMPUTER_SYSTEM) {
current_node.add_children_system_ids(downstream_node.name());
}
// Link current node <- downstream node.
if (current_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) {
// Only cable and system can have multiple upstream boards for now.
if ((downstream_fru.attributes().resource_type() != RESOURCE_TYPE_CABLE &&
downstream_fru.attributes().resource_type() !=
RESOURCE_TYPE_COMPUTER_SYSTEM) &&
!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::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.");
}
if (GetTlbmcConfig()
.fru_collector_module()
.no_associations_based_topology()) {
return absl::OkStatus();
}
// 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;
}
}
}
// Map of power port name to config key.
absl::flat_hash_map<std::string, std::string>
upstream_power_port_name_to_config_key;
for (const auto& [config_key, topology_config_node] :
topology_config.topology_config_nodes()) {
for (const auto& upstream_power_port_config :
topology_config_node.upstream_power_port_configs()) {
if (!upstream_power_port_config.port_name().empty()) {
upstream_power_port_name_to_config_key[upstream_power_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();
if (nodes_traversed.contains(current_node->config_key())) {
continue;
}
// 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);
}
// Apply the power topology
for (const auto& [power_port_name, power_port_config] :
current_node->power_port_configs()) {
auto find_downstream_power_node_key_it =
upstream_power_port_name_to_config_key.find(power_port_name);
if (find_downstream_power_node_key_it ==
upstream_power_port_name_to_config_key.end()) {
continue;
}
absl::string_view downstream_power_node_key =
find_downstream_power_node_key_it->second;
// Downstream power node is always present in the topology config so will
// not check for presence.
TopologyConfigNode& downstream_power_node =
topology_config.mutable_topology_config_nodes()->at(
downstream_power_node_key);
if (power_port_config.port_type() == PORT_TYPE_POWER_DOWNSTREAM) {
downstream_power_node.add_powered_by_ids(current_node->config_key());
current_node->add_powers_ids(downstream_power_node_key);
}
}
}
if (expected_nodes_traversed != nodes_traversed) {
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::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<std::vector<std::pair<std::string, std::string>>>
EntityConfigJsonImpl::GetFanInfoByConfigKey(
absl::string_view config_key) const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
auto config_it = topology_config->topology_config_nodes().find(config_key);
if (config_it == topology_config->topology_config_nodes().end()) {
return absl::NotFoundError(
absl::StrCat("config key not found: ", config_key));
}
std::vector<std::pair<std::string, std::string>> fan_info;
for (const auto& [fan_id, fan_key] : config_it->second.children_fans()) {
fan_info.push_back(std::make_pair(fan_id, fan_key));
}
return fan_info;
}
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["RedfishAggregatedBatchConfigs"] = nlohmann::json::array();
for (const auto& redfish_aggregated_batch_config :
data_store_.immutable_data.redfish_aggregated_batch_configs) {
json["RedfishAggregatedBatchConfigs"].push_back(
ProtoToJson(redfish_aggregated_batch_config));
}
json["RedfishAggregatedSensorConfigs"] = nlohmann::json::array();
for (const auto& redfish_aggregated_sensor_config :
data_store_.immutable_data.redfish_aggregated_sensor_configs) {
json["RedfishAggregatedSensorConfigs"].push_back(
ProtoToJson(redfish_aggregated_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,
OfflineNodeEntityInformation&& offline_node_entities,
const RawFruTable& fru_table, size_t ad_hoc_fru_count,
bool enable_deterministic_bmc)
: immutable_config_list_(std::move(config_list)),
enable_deterministic_bmc_(enable_deterministic_bmc),
offline_node_entities_(std::move(offline_node_entities)),
data_store_(PackDataIntoStore(
ReloadConfig(fru_table, ad_hoc_fru_count, ReloadType::kReloadAll),
enable_deterministic_bmc_)) {}
absl::StatusOr<std::unique_ptr<EntityConfig>> EntityConfigJsonImpl::Create(
std::vector<nlohmann::json>&& config_list,
OfflineNodeEntityInformation&& offline_node_entities,
const RawFruTable& fru_table, size_t ad_hoc_fru_count,
bool enable_deterministic_bmc) {
auto entity_config = absl::WrapUnique(new EntityConfigJsonImpl(
std::move(config_list), std::move(offline_node_entities), fru_table,
ad_hoc_fru_count, enable_deterministic_bmc));
ECCLESIA_RETURN_IF_ERROR(entity_config->GetParsedStatus());
return entity_config;
}
absl::StatusOr<std::vector<nlohmann::json>>
EntityConfigJsonImpl::ParseJsonFilesIntoConfigList(
absl::string_view config_location) {
DLOG(INFO) << "config_location: " << 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) {
DLOG(INFO) << "parsed config: " << json_file_path;
config_list.push_back(config);
}
}
}
return config_list;
}
absl::StatusOr<OfflineNodeEntityInformation>
EntityConfigJsonImpl::ParseOfflineNodeEntities(
absl::string_view offline_node_entities_path) {
if (offline_node_entities_path.empty()) {
return absl::InvalidArgumentError("OfflineNodeEntities path is empty.");
}
return ReadOfflineNodeEntityInformation(offline_node_entities_path);
}
void EntityConfigJsonImpl::SetSmartRouter(RouterInterface* smart_router) {
smart_router_ = smart_router;
}
void EntityConfigJsonImpl::SetSensorCollector(
SensorCollector* sensor_collector) {
sensor_collector_ = sensor_collector;
}
absl::StatusOr<std::vector<const Fru*>>
EntityConfigJsonImpl::GetFansByPwmSensor(
absl::string_view pwm_sensor_name) const {
ECCLESIA_ASSIGN_OR_RETURN(const FruTable* fru_table, GetAllFrus());
auto it = fru_table->pwm_sensor_to_fan_group().find(pwm_sensor_name);
if (it == fru_table->pwm_sensor_to_fan_group().end()) {
return std::vector<const Fru*>();
}
std::vector<const Fru*> fans;
for (const std::string& fan_key : it->second.fan_keys()) {
auto fru_it = fru_table->key_to_fru().find(fan_key);
if (fru_it != fru_table->key_to_fru().end()) {
fans.push_back(&fru_it->second);
}
}
return fans;
}
absl::Status EntityConfigJsonImpl::GetParsedStatus() const {
return data_store_.mutable_data.Get()->parsed_status;
}
EntityConfigJsonImplDataStore EntityConfigJsonImpl::PackDataIntoStore(
EntityConfigJsonImplData&& data, bool enable_deterministic_bmc) {
return EntityConfigJsonImplDataStore{
// TODO(b/469890768): immutable_data will be updated to
// SimpleRcu<EntityConfigJsonImplImmutableData>(
// std::move(data.immutable_data), enable_deterministic_bmc ? -1 : 0) for
// store refresh in deterministic mode. Current the refresh will only
// refresh mutable data for FRU modeling, the exposed sensor which in
// immutable data will not be updated.
.immutable_data = std::move(data.immutable_data),
// TODO(b/484338308): Remove update limit for dBMC redfish conformity.
// .mutable_data = SimpleRcu<EntityConfigJsonImplMutableData>(
// std::move(data.mutable_data),
// enable_deterministic_bmc ? -1 : data.max_updates_to_mutable_data)};
.mutable_data = SimpleRcu<EntityConfigJsonImplMutableData>(
std::move(data.mutable_data), data.max_updates_to_mutable_data)};
}
} // namespace milotic_tlbmc