blob: 50c5fff62e87c5112ccbd46b5c0ea5cce2fc07cc [file] [log] [blame]
#include "tlbmc/configs/entity_config_json_impl.h"
#include <stdint.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/log.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "g3/macros.h"
#include "time/proto.h"
#include "nlohmann/json_fwd.hpp"
#include "nlohmann/json.hpp"
#include "json_utils.h"
#include "tlbmc/central_config/config.h"
#include "entity_common_config.pb.h"
#include "tlbmc/configs/entity_config.h"
#include "tlbmc/configs/expression.h"
#include "fan_controller_config.pb.h"
#include "fan_pwm_config.pb.h"
#include "fan_tach_config.pb.h"
#include "hwmon_temp_sensor_config.pb.h"
#include "i2c_common_config.pb.h"
#include "psu_sensor_config.pb.h"
#include "reading_range_config.pb.h"
#include "shared_mem_sensor_config.pb.h"
#include "threshold_config.pb.h"
#include "topology_config.pb.h"
#include "tlbmc/rcu/simple_rcu.h"
#include "fru.pb.h"
#include "resource.pb.h"
#include "sensor.pb.h"
#include "router_interface.h"
#include "re2/re2.h"
namespace milotic_tlbmc {
namespace {
using ::crow::RouterInterface;
using ::milotic::authz::GetValueAsArray;
using ::milotic::authz::GetValueAsBool;
using ::milotic::authz::GetValueAsDoubleFromFloatOrInteger;
using ::milotic::authz::GetValueAsJson;
using ::milotic::authz::GetValueAsString;
using ::milotic::authz::GetValueAsUint;
using ::milotic::authz::GetValueAsUintFromStringOrInteger;
// ProbedConfigMap is a map of config name to ProbedConfigData, which is a
// struct containing corresponding config json and matched probed fru_keys.
using ProbedConfigMap = absl::flat_hash_map<std::string, ProbedConfigData>;
constexpr absl::string_view kRefreshIntervalKeyword = "RefreshIntervalMs";
constexpr absl::string_view kQueueSizeKeyword = "QueueSize";
constexpr absl::string_view kCableDownstreamConnectionKeyword =
"CableDownstreamConnection";
constexpr absl::string_view kCableUpstreamConnectionKeyword =
"CableUpstreamConnection";
constexpr absl::string_view kDownstreamPortKeyword = "PortDownstream";
constexpr absl::string_view kUpstreamConnectionKeyword = "UpstreamConnection";
constexpr const char* kProbeKeyword = "ProbeV2";
constexpr absl::string_view kChassisTypeKeyword = "ChassisType";
constexpr absl::string_view kStorageKeyword = "Storage";
constexpr absl::string_view kProcessorKeyword = "Processor";
constexpr absl::string_view kRelatedItemKeyword = "RelatedItem";
constexpr absl::string_view kResourceTypeKeyword = "ResourceType";
constexpr absl::string_view kPartLocationTypeKeyword = "PartLocationType";
constexpr absl::string_view kRootChassisLocationCodeKeyword =
"RootChassisLocationCode";
constexpr std::array<std::pair<std::string_view, SensorUnit>, 6>
kSupportedSensorUnits = {
// go/keep-sorted start
{
{"DegreeCelsius", SensorUnit::UNIT_DEGREE_CELSIUS},
{"Watt", SensorUnit::UNIT_WATT},
{"Ampere", SensorUnit::UNIT_AMPERE},
{"Volt", SensorUnit::UNIT_VOLT},
{"RPM", SensorUnit::UNIT_REVOLUTION_PER_MINUTE},
{"Percent", SensorUnit::UNIT_PERCENT},
}
// go/keep-sorted end
};
absl::Status ParseProcessorConfig(const nlohmann::json& config,
TopologyConfigNode& topology_config_node) {
const std::string* type_str = GetValueAsString(config, "Type");
if (type_str == nullptr || *type_str != kProcessorKeyword) {
return absl::OkStatus();
}
const std::string* processor_id = GetValueAsString(config, "Name");
if (processor_id == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not found for processor config");
}
topology_config_node.add_children_processor_ids(*processor_id);
return absl::OkStatus();
}
absl::Status ParseStorageConfig(const nlohmann::json& config,
TopologyConfigNode& topology_config_node,
size_t index) {
const nlohmann::json* storage_config =
GetValueAsJson(config, kStorageKeyword);
if (storage_config == nullptr) {
return absl::OkStatus();
}
const std::string* storage_id = GetValueAsString(*storage_config, "Id");
if (storage_id == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Id field is not found for storage config");
}
std::string storage_id_with_index = *storage_id;
absl::StrReplaceAll({{"$index", absl::StrCat(index + 1)}},
&storage_id_with_index);
topology_config_node.add_children_storage_ids(storage_id_with_index);
return absl::OkStatus();
}
SensorUnit ParseSensorUnit(const nlohmann::json& config) {
const std::string* unit_str = GetValueAsString(config, "Unit");
if (unit_str == nullptr) {
LOG(INFO) << "No unit found for: " << config.dump()
<< " setting to unknown";
return SensorUnit::UNIT_UNKNOWN;
}
const auto* it = std::lower_bound(
kSupportedSensorUnits.begin(), kSupportedSensorUnits.end(),
std::pair<std::string_view, SensorUnit>{*unit_str,
SensorUnit::UNIT_UNKNOWN},
[](const auto& a, const auto& b) { return a.first < b.first; });
if (it == kSupportedSensorUnits.end() || it->first != *unit_str) {
return SensorUnit::UNIT_UNKNOWN;
}
return it->second;
}
absl::Status ParsePortConfig(const nlohmann::json& config,
TopologyConfigNode& topology_config_node) {
const std::string* port_type_str = GetValueAsString(config, "Type");
if (port_type_str == nullptr ||
(*port_type_str != kUpstreamConnectionKeyword &&
*port_type_str != kDownstreamPortKeyword &&
*port_type_str != kCableDownstreamConnectionKeyword &&
*port_type_str != kCableUpstreamConnectionKeyword)) {
return absl::OkStatus();
}
const std::string* port_name = GetValueAsString(config, "Name");
if (port_name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not found for port");
}
PortConfig port_config;
port_config.set_port_name(*port_name);
port_config.set_port_type(PORT_TYPE_DOWNSTREAM);
if (*port_type_str == kUpstreamConnectionKeyword) {
if (topology_config_node.has_upstream_port_config()) {
return absl::InvalidArgumentError(
"Invalid config: Multiple Upstream connection configs present");
}
*topology_config_node.mutable_upstream_port_config() =
std::move(port_config);
} else if (*port_type_str == kDownstreamPortKeyword) {
const std::string* port_label = GetValueAsString(config, "Label");
if (port_label == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Label field is not found for downstream port");
}
port_config.set_port_label(*port_label);
topology_config_node.mutable_port_configs()->insert(
{*port_name, std::move(port_config)});
} else if (*port_type_str == kCableUpstreamConnectionKeyword ||
*port_type_str == kCableDownstreamConnectionKeyword) {
const std::string* port_label = GetValueAsString(config, "CableId");
if (port_label == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: CableId field is not found for cable ",
(*port_type_str == kCableUpstreamConnectionKeyword) ? "upstream"
: "downstream",
" connection"));
}
port_config.set_port_label(*port_label);
if (*port_type_str == kCableUpstreamConnectionKeyword) {
port_config.set_port_type(PORT_TYPE_CABLE_UPSTREAM);
*topology_config_node.mutable_upstream_cable_port_config() =
std::move(port_config);
} else {
port_config.set_port_type(PORT_TYPE_CABLE_DOWNSTREAM);
topology_config_node.mutable_cable_port_configs()->insert(
{*port_name, std::move(port_config)});
}
}
return absl::OkStatus();
}
ChassisType ParseChassisType(const nlohmann::json& config) {
const std::string* chassis_type_str =
GetValueAsString(config, kChassisTypeKeyword);
ChassisType chassis_type;
if (chassis_type_str == nullptr ||
!ChassisType_Parse(*chassis_type_str, &chassis_type)) {
LOG(INFO) << "No valid chassis type found for: " << config.dump()
<< " setting to default RackMount";
chassis_type = CHASSIS_TYPE_RACK_MOUNT;
}
return chassis_type;
}
absl::StatusOr<ResourceType> ParseResourceType(const nlohmann::json& config) {
const std::string* resource_type_str =
GetValueAsString(config, kResourceTypeKeyword);
if (resource_type_str == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: No resource type found for config ", config.dump()));
}
ResourceType resource_type;
if (!ResourceType_Parse(*resource_type_str, &resource_type)) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Invalid resource type found for config: ",
*resource_type_str));
}
return resource_type;
}
void ParsePartLocationType(const nlohmann::json& config,
TopologyConfigNode& topology_config_node) {
const std::string* location_type_str =
GetValueAsString(config, kPartLocationTypeKeyword);
if (location_type_str == nullptr) {
LOG(INFO) << "No location type found for config: " << config.dump();
return;
}
PartLocationType location_type;
if (!PartLocationType_Parse(*location_type_str, &location_type)) {
LOG(WARNING) << "Invalid resource type found for config: "
<< *location_type_str;
return;
}
topology_config_node.mutable_location_context()->set_location_type(
location_type);
}
void ParseRootChassisLocationCode(const nlohmann::json& config,
TopologyConfigNode& topology_config_node) {
const std::string* root_chassis_location_code =
GetValueAsString(config, kRootChassisLocationCodeKeyword);
if (root_chassis_location_code != nullptr) {
DLOG(INFO) << "Root chassis location code found: "
<< *root_chassis_location_code;
topology_config_node.set_root_chassis_location_code(
*root_chassis_location_code);
}
}
std::optional<RelatedItem> ParseRelatedItem(const nlohmann::json& config) {
const nlohmann::json* related_item_json =
GetValueAsJson(config, kRelatedItemKeyword);
if (related_item_json == nullptr) {
LOG(INFO) << "No related item found for config: " << config.dump();
return std::nullopt;
}
const std::string* id = GetValueAsString(*related_item_json, "Id");
if (id == nullptr) {
LOG(INFO) << "No item name found for related item config: "
<< config.dump();
return std::nullopt;
}
ResourceType type;
const std::string* type_str = GetValueAsString(*related_item_json, "Type");
if (type_str == nullptr || !ResourceType_Parse(*type_str, &type)) {
type = RESOURCE_TYPE_BOARD;
}
RelatedItem related_item;
related_item.set_id(*id);
related_item.set_type(type);
return related_item;
}
absl::Status ParseAssetConfig(const nlohmann::json& config, Fru& fru_object) {
const nlohmann::json* asset_config_ptr = GetValueAsJson(config, "Asset");
if (asset_config_ptr == nullptr) {
return absl::OkStatus();
}
AssetInfo asset;
nlohmann::json asset_config = *asset_config_ptr;
const std::string* manufacturer_ptr =
GetValueAsString(asset_config, "Manufacturer");
if (manufacturer_ptr != nullptr) {
if (absl::AsciiStrToUpper(*manufacturer_ptr) == "$BOARD_MANUFACTURER" &&
fru_object.data().fru_info().has_board_manufacturer()) {
asset.set_manufacturer(fru_object.data().fru_info().board_manufacturer());
} else if (absl::AsciiStrToUpper(*manufacturer_ptr) ==
"$PRODUCT_MANUFACTURER" &&
fru_object.data().fru_info().has_product_manufacturer()) {
asset.set_manufacturer(
fru_object.data().fru_info().product_manufacturer());
} else if (absl::StartsWith(*manufacturer_ptr, "$")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid Asset config, cannot substitute ", *manufacturer_ptr,
". Check this is a valid substitution keyword and scanned Fru "
"contains this field. Config: ",
config.dump()));
} else {
asset.set_manufacturer(*manufacturer_ptr);
}
}
const std::string* model_ptr = GetValueAsString(asset_config, "Model");
if (model_ptr != nullptr) {
if (absl::AsciiStrToUpper(*model_ptr) == "$BOARD_PRODUCT_NAME" &&
fru_object.data().fru_info().has_board_product_name()) {
asset.set_product_name(fru_object.data().fru_info().board_product_name());
} else if (absl::AsciiStrToUpper(*model_ptr) == "$PRODUCT_PRODUCT_NAME" &&
fru_object.data().fru_info().has_product_product_name()) {
asset.set_product_name(
fru_object.data().fru_info().product_product_name());
} else if (absl::StartsWith(*model_ptr, "$")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid Asset config, cannot substitute ", *model_ptr,
". Check this is a valid substitution keyword and scanned Fru "
"contains this field."));
} else {
asset.set_product_name(*model_ptr);
}
}
const std::string* part_number_ptr =
GetValueAsString(asset_config, "PartNumber");
if (part_number_ptr != nullptr) {
if (absl::AsciiStrToUpper(*part_number_ptr) == "$BOARD_PART_NUMBER" &&
fru_object.data().fru_info().has_board_part_number()) {
asset.set_part_number(fru_object.data().fru_info().board_part_number());
} else if (absl::AsciiStrToUpper(*part_number_ptr) ==
"$PRODUCT_PART_NUMBER" &&
fru_object.data().fru_info().has_product_part_number()) {
asset.set_part_number(fru_object.data().fru_info().product_part_number());
} else if (absl::StartsWith(*part_number_ptr, "$")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid Asset config, cannot substitute ", *part_number_ptr,
". Check this is a valid substitution keyword and scanned Fru "
"contains this field."));
} else {
asset.set_part_number(*part_number_ptr);
}
}
const std::string* serial_number_ptr =
GetValueAsString(asset_config, "SerialNumber");
if (serial_number_ptr != nullptr) {
if (absl::AsciiStrToUpper(*serial_number_ptr) == "$BOARD_SERIAL_NUMBER" &&
fru_object.data().fru_info().has_board_serial_number()) {
asset.set_serial_number(
fru_object.data().fru_info().board_serial_number());
} else if (absl::AsciiStrToUpper(*serial_number_ptr) ==
"$PRODUCT_SERIAL_NUMBER" &&
fru_object.data().fru_info().has_product_serial_number()) {
asset.set_serial_number(
fru_object.data().fru_info().product_serial_number());
} else if (absl::StartsWith(*serial_number_ptr, "$")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid Asset config, cannot substitute ", *serial_number_ptr,
". Check this is a valid substitution keyword and scanned Fru "
"contains this field."));
} else {
asset.set_serial_number(*serial_number_ptr);
}
}
*fru_object.mutable_data()->mutable_asset_info() = asset;
return absl::OkStatus();
}
absl::StatusOr<nlohmann::json> GetObject(const nlohmann::json& json_obj,
const std::string& key) {
auto find_key = json_obj.find(key);
if (find_key == json_obj.end()) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: ", key, " field is not found: ", json_obj.dump()));
}
return find_key->get<nlohmann::json>();
}
bool IsSubFru(const nlohmann::json& probe) {
const bool* is_sub_fru = GetValueAsBool(probe, "IsSubFru");
if (is_sub_fru == nullptr) {
return false;
}
return *is_sub_fru;
}
absl::StatusOr<std::string> ParseConfigName(const nlohmann::json& config) {
const std::string* name = GetValueAsString(config, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Name field is not found: ", config.dump()));
}
if (name->empty()) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid config: Name field is empty: ", config.dump()));
}
return *name;
}
bool CheckProbeFieldMatch(const std::string* probe_field, const Fru& fru,
absl::string_view fru_field_name) {
auto find_fru_field = fru.data().fields().find(fru_field_name);
return probe_field == nullptr ||
(find_fru_field != fru.data().fields().end() &&
probe_field != nullptr &&
RE2::FullMatch(find_fru_field->second, *probe_field));
}
absl::StatusOr<ProbedConfigMap> ProcessConfigs(
const std::vector<nlohmann::json>& config_list, FruTable& fru_table) {
ProbedConfigMap probed_config_map;
for (const auto& config : config_list) {
absl::StatusOr<nlohmann::json> probe_result =
GetObject(config, kProbeKeyword);
if (!probe_result.ok()) {
continue;
}
// Find name of the config.
ECCLESIA_ASSIGN_OR_RETURN(std::string name, ParseConfigName(config));
name = absl::StrReplaceAll(name, {{" ", "_"}});
const std::string* true_str = probe_result->get_ptr<const std::string*>();
bool true_value = true_str != nullptr && *true_str == "TRUE";
// Check if the probe is a true value.
if (true_value) {
if (probed_config_map.contains(name)) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Multiple configs with TRUE probe have"
" the same name: ", name));
}
auto& probed_data = probed_config_map[name];
probed_data.fru_keys.push_back(FruKey(name));
probed_data.config = config;
Fru entity_object;
entity_object.mutable_attributes()->set_key(name);
entity_object.mutable_attributes()->set_status(STATUS_READY);
fru_table.mutable_key_to_fru()->insert({name, entity_object});
continue;
}
bool skip_value = true_str != nullptr && *true_str == "FALSE";
if (skip_value) {
continue;
}
nlohmann::json probe = *probe_result;
// Perform probe.
// Match Fru if config contains IPMI FRU probe.
auto ipmi_fru_it = probe.find("IpmiFru");
if (ipmi_fru_it == probe.end()) {
return absl::InvalidArgumentError(
"Invalid config: IpmiFru field is not a valid probe");
}
auto ipmi_fru_object = ipmi_fru_it->get<nlohmann::json>();
// Wrap single IpmiFru object in array to simplify logic below.
if (!ipmi_fru_object.is_array()) {
ipmi_fru_object = nlohmann::json::array({ipmi_fru_object});
}
for (const auto& ipmi_fru : ipmi_fru_object) {
// Get probe fields.
const auto* board_product_name =
GetValueAsString(ipmi_fru, "BOARD_PRODUCT_NAME");
const auto* product_product_name =
GetValueAsString(ipmi_fru, "PRODUCT_PRODUCT_NAME");
const auto* bus = GetValueAsUint(ipmi_fru, "BUS");
const auto* address = GetValueAsUint(ipmi_fru, "ADDRESS");
const auto* part_number = GetValueAsString(ipmi_fru, "BOARD_PART_NUMBER");
const auto* product_info_am2 =
GetValueAsString(ipmi_fru, "PRODUCT_INFO_AM2");
if (board_product_name == nullptr && product_product_name == nullptr &&
bus == nullptr && address == nullptr && part_number == nullptr &&
product_info_am2 == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: IpmiFru field is not a valid probe");
}
// Check if the FRU matches the probe.
for (const auto& [key, fru] : fru_table.key_to_fru()) {
bool i2c_info_matches_or_null = false;
if ((bus == nullptr && address == nullptr) ||
(bus != nullptr && *bus == fru.i2c_info().bus() &&
address != nullptr && *address == fru.i2c_info().address())) {
i2c_info_matches_or_null = true;
}
// Check if any of the FRU fields match the probe.
bool board_product_name_matches_or_null =
CheckProbeFieldMatch(board_product_name, fru, "BOARD_PRODUCT_NAME");
bool product_product_name_matches_or_null = CheckProbeFieldMatch(
product_product_name, fru, "PRODUCT_PRODUCT_NAME");
bool part_number_matches_or_null =
CheckProbeFieldMatch(part_number, fru, "BOARD_PART_NUMBER");
bool product_info_am2_matches_or_null =
CheckProbeFieldMatch(product_info_am2, fru, "PRODUCT_INFO_AM2");
// If the FRU doesn't match the probe, continue to the next FRU.
if (!i2c_info_matches_or_null || !board_product_name_matches_or_null ||
!product_product_name_matches_or_null ||
!part_number_matches_or_null || !product_info_am2_matches_or_null) {
continue;
}
// This condition will check that
// 1. Only configs containing in $index can have multiple Fru probe true
// 2. A Fru cannot match multiple probes within one config
// 3. Multiple configs cannot probe true with the same name
if (probed_config_map.contains(name) &&
!absl::StrContains(name, "$index")) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Frus match the same config multiple"
" times but config name does not contain $index : ",
name));
}
// If we reach here, the FRU matches the probe and is unique.
auto& probed_data = probed_config_map[name];
// If the key is in the format of "bus:address", we will parse the bus
// and address and use them to construct the FruKey object. Otherwise,
// default to the string representation of the FruKey.
probed_data.fru_keys.push_back(FruKey(key));
probed_data.config = config;
LOG(INFO) << "Matched config: " << name << " with FRU: " << key;
}
// Sort the found FRU keys for each config.
if (auto probed_data_it = probed_config_map.find(name);
probed_data_it != probed_config_map.end()) {
std::sort(probed_data_it->second.fru_keys.begin(),
probed_data_it->second.fru_keys.end());
}
}
}
return probed_config_map;
}
void SortChildrenResourceIds(TopologyConfig& topology_config) {
for (auto& [_, node] : *topology_config.mutable_topology_config_nodes()) {
std::sort(node.mutable_children_chassis_ids()->pointer_begin(),
node.mutable_children_chassis_ids()->pointer_end(),
[](const std::string* id_1, const std::string* id_2) {
return *id_1 < *id_2;
});
std::sort(node.mutable_children_cable_ids()->pointer_begin(),
node.mutable_children_cable_ids()->pointer_end(),
[](const std::string* id_1, const std::string* id_2) {
return *id_1 < *id_2;
});
std::sort(node.mutable_children_processor_ids()->pointer_begin(),
node.mutable_children_processor_ids()->pointer_end(),
[](const std::string* id_1, const std::string* id_2) {
return *id_1 < *id_2;
});
std::sort(node.mutable_children_storage_ids()->pointer_begin(),
node.mutable_children_storage_ids()->pointer_end(),
[](const std::string* id_1, const std::string* id_2) {
return *id_1 < *id_2;
});
}
}
} // namespace
template <class TypeName>
struct TypeComparator {
// Compared by the first element which is the type name.
bool operator()(const std::pair<std::string_view, TypeName>& lhs,
const std::pair<std::string_view, TypeName>& rhs) const {
return lhs.first < rhs.first;
}
};
constexpr std::array<std::pair<std::string_view, HwmonTempSensorType>, 3>
kSupportedHwmonTempSensorTypes = {
// go/keep-sorted start numeric=yes
{{"MAX31725", HWMON_TEMP_SENSOR_TYPE1_MAX31725},
{"MAX31732", HWMON_TEMP_SENSOR_TYPE3_MAX31732},
{"TMP75", HWMON_TEMP_SENSOR_TYPE2_TMP75}},
// go/keep-sorted end
};
constexpr std::array<std::pair<std::string_view, PsuSensorType>, 8>
kSupportedPsuSensorTypes = {
// go/keep-sorted start
{{"ADM1266", PSU_SENSOR_TYPE1_ADM1266},
{"ADM1272", PSU_SENSOR_TYPE2_ADM1272},
{"LTC2991", PSU_SENSOR_TYPE4_LTC2991},
{"RAA228228", PSU_SENSOR_TYPE5_RAA228228},
{"TDA38725", PSU_SENSOR_TYPE6_TDA38725},
{"TDA38740", PSU_SENSOR_TYPE7_TDA38740},
{"XDPE1A2G5B", PSU_SENSOR_TYPE8_XDPE1A2G5B},
{"pmbus", PSU_SENSOR_TYPE3_PMBUS}}
// go/keep-sorted end
};
constexpr std::array<std::pair<std::string_view, FanControllerType>, 1>
kSupportedFanControllerTypes = {
// go/keep-sorted start
{{"MAX31790", FAN_CONTROLLER_TYPE_MAX31790}}
// go/keep-sorted end
};
constexpr std::array<std::pair<std::string_view, FanPwmType>, 1>
kSupportedFanPwmTypes = {
// go/keep-sorted start
{{"I2CFan", PWM_SENSOR_TYPE_I2C_FAN}}
// go/keep-sorted end
};
constexpr std::array<std::pair<std::string_view, FanTachType>, 1>
kSupportedFanTachTypes = {
// go/keep-sorted start
{{"I2CFan", TACH_SENSOR_TYPE_I2C_FAN}}
// go/keep-sorted end
};
constexpr std::array<std::pair<std::string_view, SharedMemSensorType>, 1>
kSupportedSharedMemSensorTypes = {
// go/keep-sorted start
{{"SharedMemSensor", SHARED_MEM_SENSOR_TYPE_TYPE1_NUMERIC_SENSOR}}
// go/keep-sorted end
};
HwmonTempSensorType EntityConfigJsonImpl::IsHwmonTempSensor(
std::string_view type) {
const auto* it =
std::lower_bound(kSupportedHwmonTempSensorTypes.begin(),
kSupportedHwmonTempSensorTypes.end(),
std::pair<std::string_view, HwmonTempSensorType>{
type, HWMON_TEMP_SENSOR_TYPE0_UNKNOWN},
TypeComparator<HwmonTempSensorType>());
if (it == kSupportedHwmonTempSensorTypes.end() || it->first != type) {
return HWMON_TEMP_SENSOR_TYPE0_UNKNOWN;
}
return it->second;
}
PsuSensorType EntityConfigJsonImpl::IsPsuSensor(std::string_view type) {
const auto* it = std::lower_bound(kSupportedPsuSensorTypes.begin(),
kSupportedPsuSensorTypes.end(),
std::pair<std::string_view, PsuSensorType>{
type, PSU_SENSOR_TYPE0_UNKNOWN},
TypeComparator<PsuSensorType>());
if (it == kSupportedPsuSensorTypes.end() || it->first != type) {
return PSU_SENSOR_TYPE0_UNKNOWN;
}
return it->second;
}
FanControllerType EntityConfigJsonImpl::IsFanController(std::string_view type) {
const auto* it = std::lower_bound(
kSupportedFanControllerTypes.begin(), kSupportedFanControllerTypes.end(),
std::pair<std::string_view, FanControllerType>{
type, FAN_CONTROLLER_TYPE_UNKNOWN},
TypeComparator<FanControllerType>());
if (it == kSupportedFanControllerTypes.end() || it->first != type) {
return FAN_CONTROLLER_TYPE_UNKNOWN;
}
return it->second;
}
FanPwmType EntityConfigJsonImpl::IsFanPwm(std::string_view type) {
const auto* it = std::lower_bound(
kSupportedFanPwmTypes.begin(), kSupportedFanPwmTypes.end(),
std::pair<std::string_view, FanPwmType>{type, PWM_SENSOR_TYPE_UNKNOWN},
TypeComparator<FanPwmType>());
if (it == kSupportedFanPwmTypes.end() || it->first != type) {
return PWM_SENSOR_TYPE_UNKNOWN;
}
return it->second;
}
FanTachType EntityConfigJsonImpl::IsFanTach(std::string_view type) {
const auto* it = std::lower_bound(
kSupportedFanTachTypes.begin(), kSupportedFanTachTypes.end(),
std::pair<std::string_view, FanTachType>{type, TACH_SENSOR_TYPE_UNKNOWN},
TypeComparator<FanTachType>());
if (it == kSupportedFanTachTypes.end() || it->first != type) {
return TACH_SENSOR_TYPE_UNKNOWN;
}
return it->second;
}
SharedMemSensorType EntityConfigJsonImpl::IsSharedMemSensor(
std::string_view type) {
const auto* it =
std::lower_bound(kSupportedSharedMemSensorTypes.begin(),
kSupportedSharedMemSensorTypes.end(),
std::pair<std::string_view, SharedMemSensorType>{
type, SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN},
TypeComparator<SharedMemSensorType>());
if (it == kSupportedSharedMemSensorTypes.end() || it->first != type) {
return SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN;
}
return it->second;
}
absl::Status EntityConfigJsonImpl::ParseAndPopulateConfig(
EntityConfigJsonImplImmutableData& data, const nlohmann::json& element,
absl::string_view config_name_with_index) {
const bool* tlbmc_owned = GetValueAsBool(element, "TlbmcOwned");
if (tlbmc_owned == nullptr || !*tlbmc_owned) {
return absl::OkStatus();
}
const std::string* type = GetValueAsString(element, "Type");
if (type == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a string");
}
// HWMon temperature sensor.
HwmonTempSensorType hwmon_temp_sensor_type = IsHwmonTempSensor(*type);
if (hwmon_temp_sensor_type != HWMON_TEMP_SENSOR_TYPE0_UNKNOWN) {
absl::StatusOr<HwmonTempSensorConfig> hwmon_temp_sensor_config =
ParseHwmonTempSensorConfig(hwmon_temp_sensor_type, element);
if (!hwmon_temp_sensor_config.ok()) {
return hwmon_temp_sensor_config.status();
}
*hwmon_temp_sensor_config->mutable_entity_common_config()
->mutable_board_config_name() = config_name_with_index;
if (!hwmon_temp_sensor_config->entity_common_config().has_related_item()) {
// By default, if no related item is specified, the sensor related item
// will be its chassis
RelatedItem default_related_chassis;
default_related_chassis.set_id(
hwmon_temp_sensor_config->entity_common_config().board_config_name());
default_related_chassis.set_type(RESOURCE_TYPE_BOARD);
*hwmon_temp_sensor_config->mutable_entity_common_config()
->mutable_related_item() = default_related_chassis;
}
data.hwmon_temp_sensor_configs.push_back(*hwmon_temp_sensor_config);
}
// PSU sensor.
PsuSensorType psu_sensor_type = IsPsuSensor(*type);
if (psu_sensor_type != PSU_SENSOR_TYPE0_UNKNOWN) {
absl::StatusOr<PsuSensorConfig> psu_sensor_config =
ParsePsuSensorConfig(psu_sensor_type, element);
if (!psu_sensor_config.ok()) {
return psu_sensor_config.status();
}
*psu_sensor_config->mutable_entity_common_config()
->mutable_board_config_name() = config_name_with_index;
if (!psu_sensor_config->entity_common_config().has_related_item()) {
// By default, if no related item is specified, the sensor related item
// will be its chassis
RelatedItem default_related_chassis;
default_related_chassis.set_id(
psu_sensor_config->entity_common_config().board_config_name());
default_related_chassis.set_type(RESOURCE_TYPE_BOARD);
*psu_sensor_config->mutable_entity_common_config()
->mutable_related_item() = default_related_chassis;
}
data.psu_sensor_configs.push_back(*psu_sensor_config);
}
// FAN controller
FanControllerType fan_controller_type = IsFanController(*type);
if (fan_controller_type != FAN_CONTROLLER_TYPE_UNKNOWN) {
absl::StatusOr<FanControllerConfig> fan_controller_config =
ParseFanControllerConfig(fan_controller_type, element);
if (!fan_controller_config.ok()) {
return fan_controller_config.status();
}
data.fan_controller_configs.push_back(*fan_controller_config);
}
FanPwmType fan_pwm_type = IsFanPwm(*type);
if (fan_pwm_type != PWM_SENSOR_TYPE_UNKNOWN) {
absl::StatusOr<FanPwmConfig> fan_pwm_config =
ParseFanPwmConfig(fan_pwm_type, element);
if (!fan_pwm_config.ok()) {
return fan_pwm_config.status();
}
*fan_pwm_config->mutable_entity_common_config()
->mutable_board_config_name() = config_name_with_index;
data.fan_pwm_configs.push_back(*fan_pwm_config);
}
FanTachType fan_tach_type = IsFanTach(*type);
if (fan_tach_type != TACH_SENSOR_TYPE_UNKNOWN) {
absl::StatusOr<FanTachConfig> fan_tach_config =
ParseFanTachConfig(fan_tach_type, element);
if (!fan_tach_config.ok()) {
return fan_tach_config.status();
}
*fan_tach_config->mutable_entity_common_config()
->mutable_board_config_name() = config_name_with_index;
data.fan_tach_configs.push_back(*fan_tach_config);
}
SharedMemSensorType shared_mem_sensor_type = IsSharedMemSensor(*type);
if (shared_mem_sensor_type != SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN) {
absl::StatusOr<SharedMemSensorConfig> shared_mem_sensor_config =
ParseSharedMemSensorConfig(shared_mem_sensor_type, element);
if (!shared_mem_sensor_config.ok()) {
return shared_mem_sensor_config.status();
}
*shared_mem_sensor_config->mutable_entity_common_config()
->mutable_board_config_name() = config_name_with_index;
data.shared_mem_sensor_configs.push_back(*shared_mem_sensor_config);
}
return absl::OkStatus();
}
void EntityConfigJsonImpl::UpdateFruAndTopology(const RawFruTable& fru_table) {
EntityConfigJsonImplData new_data = ReloadConfig(
fru_table, 0,
/*reload_fru_and_topology_only=*/ReloadType::kReloadFruAndTopologyOnly);
if (!new_data.mutable_data.parsed_status.ok()) {
LOG(WARNING) << "Failed to parse config: "
<< new_data.mutable_data.parsed_status;
}
// Update the mutable data. Should not fail.
absl::Status update_status =
data_store_.mutable_data.Update(std::move(new_data.mutable_data));
if (!update_status.ok()) {
LOG(WARNING) << "Failed to update FRU and topology: " << update_status;
}
if (smart_router_ != nullptr) {
smart_router_->UpdateTlbmcOwnedUrls();
}
}
EntityConfigJsonImplData EntityConfigJsonImpl::ReloadConfig(
const RawFruTable& fru_table, size_t ad_hoc_fru_count,
ReloadType reload_type) {
EntityConfigJsonImplImmutableData immutable_data;
EntityConfigJsonImplMutableData mutable_data;
// Make Readonly copy of the FRU table.
for (const auto& [key, raw_fru] : fru_table.key_to_raw_fru()) {
Fru fru;
Attributes* attributes = fru.mutable_attributes();
attributes->set_key(raw_fru.key());
attributes->set_status(Status::STATUS_READY);
attributes->mutable_refresh_policy()->set_refresh_mode(
RefreshMode::REFRESH_MODE_ON_DEMAND);
*fru.mutable_data() = raw_fru.data();
*fru.mutable_i2c_info() = raw_fru.i2c_info();
mutable_data.fru_table.mutable_key_to_fru()->insert({key, fru});
}
absl::StatusOr<ProbedConfigMap> probed_config_map =
ProcessConfigs(immutable_config_list_, mutable_data.fru_table);
if (!probed_config_map.ok()) {
mutable_data.parsed_status = probed_config_map.status();
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
if (probed_config_map->empty()) {
mutable_data.parsed_status =
absl::InvalidArgumentError("Probe failed for all configs");
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
// This map is used to update the FRU info for it's sub FRUs.
// Example:
// {"fru_key_1": {"sub_fru_config_1", "sub_fru_config_2"},
// "fru_key_2": {"sub_fru_config_3"}}
absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
fru_key_to_sub_fru_config;
// If multiple FRUs match the config, we need to substitute the FRU
// specific fields in the config with the FRU info and then parse the
// config.
for (const auto& [name, config_data] : *probed_config_map) {
// Check if config belongs to a sub FRU.
// We have already checked that the config has a probe in `ProcessConfigs`.
bool is_subfru = IsSubFru(config_data.config.at(kProbeKeyword));
// For each FRU, substitute config variables with FRU info and parse the
// config.
// `ProcessConfigs` is already checked to guarantee that only configs with
// $index will have multiple Fru keys matching.
for (size_t i = 0; i < config_data.fru_keys.size(); i++) {
// Create a new topology config node for each FRU.
TopologyConfigNode topology_config_node;
std::string config_name_with_index = name;
absl::StrReplaceAll({{"$index", std::to_string(i + 1)}},
&config_name_with_index);
topology_config_node.set_name(config_name_with_index);
const std::string fru_key = config_data.fru_keys[i].ToString();
absl::StatusOr<ResourceType> resource_type =
ParseResourceType(config_data.config);
if (!resource_type.ok()) {
mutable_data.parsed_status = resource_type.status();
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
// Get the FRU object from the fru_key. This is used to update the Fru
// object fields with Fru information from config.
// Key is not expected to be invalid.
Fru& fru = mutable_data.fru_table.mutable_key_to_fru()->at(fru_key);
fru.mutable_attributes()->set_resource_type(*resource_type);
if (*resource_type == RESOURCE_TYPE_BOARD) {
fru.mutable_attributes()->set_chassis_type(
ParseChassisType(config_data.config));
}
ParsePartLocationType(config_data.config, topology_config_node);
ParseRootChassisLocationCode(config_data.config, topology_config_node);
if (is_subfru &&
topology_config_node.location_context().location_type() !=
PART_LOCATION_TYPE_EMBEDDED) {
mutable_data.parsed_status = absl::InternalError(absl::StrCat(
"Invalid config: Sub FRU ", topology_config_node.name(),
" has part location type ",
topology_config_node.location_context().location_type(),
". All sub FRUs should have part location type "
"PART_LOCATION_TYPE_EMBEDDED."));
}
absl::StatusOr<nlohmann::json> exposes =
GetObject(config_data.config, "Exposes");
if (!exposes.ok()) {
mutable_data.parsed_status = exposes.status();
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
const auto* elements = exposes->get_ptr<const nlohmann::json::array_t*>();
if (elements == nullptr) {
mutable_data.parsed_status = absl::InvalidArgumentError(absl::StrCat(
"Invalid config: The JSON field `Exposes` is not an array", name));
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
// Get the FRU info for the FRU key. This is used to substitute the FRU
// specific fields in the config.
// Key is not expected to be invalid.
const Fru& fru_info = mutable_data.fru_table.key_to_fru().at(fru_key);
for (const auto& element_before_fru_substitution : *elements) {
// Substitute FRU specific fields.
nlohmann::json element = element_before_fru_substitution;
// Substitute Bus field.
const std::string* config_bus = GetValueAsString(element, "Bus");
if (config_bus != nullptr && fru_info.has_i2c_info() &&
absl::StrContainsIgnoreCase(*config_bus, "$bus")) {
element["Bus"] = fru_info.i2c_info().bus();
}
// Substitute Address field.
const std::string* config_address_str =
GetValueAsString(element, "Address");
if (config_address_str != nullptr &&
absl::StrContainsIgnoreCase(*config_address_str, "$address") &&
fru_info.has_i2c_info()) {
element["Address"] =
absl::StrCat(absl::Hex(fru_info.i2c_info().address()));
}
// Substitute $bus and $index in values of all remaining elements in the
// config.
for (auto& [key, field_value] : element.items()) {
const std::string* field_value_str = GetValueAsString(element, key);
// Special case for RelatedItem, we know this will be nested and want
// substitution for it.
if (key == "RelatedItem") {
const std::string* item_id = GetValueAsString(field_value, "Id");
if (item_id != nullptr) {
field_value["Id"] = absl::StrReplaceAll(
*item_id, {{"$index", absl::StrCat(i + 1)}});
}
}
// If the field is not a string, continue since we expect substitution
// only for string fields.
if (field_value_str == nullptr) {
continue;
}
// Make a copy of the field value string since we will be modifying
// it in place.
std::string field_value_str_copy = *field_value_str;
if (absl::StrContainsIgnoreCase(field_value_str_copy, "$bus")) {
absl::StatusOr<std::string> field_value_substitute_result =
EvaluateStrExpressionSubstitution(
field_value_str_copy,
absl::StrContains(field_value_str_copy, "$bus") ? "$bus"
: "$BUS",
absl::StrCat(fru_info.i2c_info().bus()));
if (!field_value_substitute_result.ok()) {
mutable_data.parsed_status =
field_value_substitute_result.status();
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
element[key] = *field_value_substitute_result;
}
if (absl::StrContains(field_value_str_copy, "$index")) {
absl::StatusOr<std::string> field_value_substitute_result =
EvaluateStrExpressionSubstitution(
field_value_str_copy, "$index", absl::StrCat(i + 1));
if (!field_value_substitute_result.ok()) {
mutable_data.parsed_status =
field_value_substitute_result.status();
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
element[key] = *field_value_substitute_result;
}
}
if (reload_type == ReloadType::kReloadAll) {
absl::Status config_parse_status = ParseAndPopulateConfig(
immutable_data, element, config_name_with_index);
if (!config_parse_status.ok()) {
mutable_data.parsed_status = config_parse_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
}
absl::Status parse_port_config_status =
ParsePortConfig(element, topology_config_node);
if (!parse_port_config_status.ok()) {
mutable_data.parsed_status = parse_port_config_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
absl::Status parse_processor_status =
ParseProcessorConfig(element, topology_config_node);
if (!parse_processor_status.ok()) {
mutable_data.parsed_status = parse_processor_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
}
absl::Status parse_storage_status =
ParseStorageConfig(config_data.config, topology_config_node, i);
if (!parse_storage_status.ok()) {
mutable_data.parsed_status = parse_storage_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
absl::Status parse_asset_config_status = ParseAssetConfig(
config_data.config, fru);
if (!parse_asset_config_status.ok()) {
mutable_data.parsed_status = parse_asset_config_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
topology_config_node.mutable_fru_info()->set_fru_key(fru_key);
if (!is_subfru) {
mutable_data.topology_config.mutable_fru_configs()->insert(
{fru_key, config_name_with_index});
} else {
topology_config_node.mutable_fru_info()->set_is_sub_fru(true);
fru_key_to_sub_fru_config[fru_key].insert(config_name_with_index);
}
if (fru_info.attributes().resource_type() == RESOURCE_TYPE_CABLE) {
topology_config_node.set_not_owned_by_tlbmc(true);
}
// If the config has port configs, add the topology config node to the
// topology config.
if (topology_config_node.has_upstream_port_config() ||
!topology_config_node.port_configs().empty() ||
topology_config_node.has_upstream_cable_port_config() ||
!topology_config_node.cable_port_configs().empty()) {
mutable_data.topology_config.mutable_topology_config_nodes()->insert(
{std::move(config_name_with_index),
std::move(topology_config_node)});
}
}
}
// Update the FRU info for sub FRUs.
for (auto& [fru_key, config_names] : fru_key_to_sub_fru_config) {
for (const auto& config_name : config_names) {
auto it =
mutable_data.topology_config.mutable_fru_configs()->find(fru_key);
if (it == mutable_data.topology_config.mutable_fru_configs()->end()) {
mutable_data.parsed_status = absl::InternalError(
absl::StrCat("Fru config not found for Sub FRU defined in config: ",
config_name));
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
std::string config_name_with_index = it->second;
// Topology config is expected to be present in the map as the name is
// obtained from the map itself.
TopologyConfigNode& topology_config_node =
mutable_data.topology_config.mutable_topology_config_nodes()->at(
config_name_with_index);
*topology_config_node.mutable_sub_fru_config_names()->Add() = config_name;
}
}
absl::flat_hash_set<absl::string_view> fru_keys_with_sub_frus;
for (const auto& [fru_key, _] : mutable_data.fru_table.key_to_fru()) {
auto it = mutable_data.topology_config.mutable_fru_configs()->find(fru_key);
if (it == mutable_data.topology_config.mutable_fru_configs()->end()) {
if (!GetTlbmcConfig().allow_dangling_frus) {
mutable_data.parsed_status = absl::InternalError(absl::StrCat(
"Invalid topology: no config with matching probe found for FRU: ",
fru_key));
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
continue;
}
fru_keys_with_sub_frus.insert(it->second);
}
for (const auto& [fru_key, sub_fru_config_names] :
fru_key_to_sub_fru_config) {
fru_keys_with_sub_frus.insert(sub_fru_config_names.begin(),
sub_fru_config_names.end());
}
absl::Status create_associations_status =
CreateAssociationsBetweenTopologyConfigNodes(
mutable_data.topology_config, mutable_data.fru_table,
std::move(fru_keys_with_sub_frus));
if (!create_associations_status.ok()) {
mutable_data.parsed_status = create_associations_status;
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
// Sort the children of each Fru
SortChildrenResourceIds(mutable_data.topology_config);
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
.max_updates_to_mutable_data = ad_hoc_fru_count};
}
ReadingRangeConfigs EntityConfigJsonImpl::ParseReadingRangeConfigs(
const nlohmann::json& config, std::optional<double> default_min,
std::optional<double> default_max) {
ReadingRangeConfigs reading_range_configs;
std::optional<double> max =
GetValueAsDoubleFromFloatOrInteger(config, "MaxReading");
if (max.has_value() || default_max) {
ReadingRangeConfig max_range;
max_range.set_type(READING_RANGE_TYPE_MAX);
max_range.set_reading(!max.has_value() ? *default_max : *max);
*reading_range_configs.mutable_reading_range_configs()->Add() = max_range;
}
std::optional<double> min =
GetValueAsDoubleFromFloatOrInteger(config, "MinReading");
if (min.has_value() || default_min) {
ReadingRangeConfig min_range;
min_range.set_type(READING_RANGE_TYPE_MIN);
min_range.set_reading(!min.has_value() ? *default_min : *min);
*reading_range_configs.mutable_reading_range_configs()->Add() = min_range;
}
return reading_range_configs;
}
std::unordered_map<std::string, ReadingRangeConfigs>
EntityConfigJsonImpl::ParseReadingRangeConfigs(
const nlohmann::json& config, absl::Span<const std::string> labels) {
std::unordered_map<std::string, ReadingRangeConfigs>
label_to_reading_range_configs;
for (const std::string& label : labels) {
std::string max_key = absl::Substitute("$0_Max", label);
std::optional<double> max =
GetValueAsDoubleFromFloatOrInteger(config, max_key);
if (max.has_value()) {
ReadingRangeConfig max_range;
max_range.set_type(READING_RANGE_TYPE_MAX);
max_range.set_reading(*max);
ReadingRangeConfigs& max_configs = label_to_reading_range_configs[label];
*max_configs.mutable_reading_range_configs()->Add() = max_range;
}
std::string min_key = absl::Substitute("$0_Min", label);
std::optional<double> min =
GetValueAsDoubleFromFloatOrInteger(config, min_key);
if (min.has_value()) {
ReadingRangeConfig min_range;
min_range.set_type(READING_RANGE_TYPE_MIN);
min_range.set_reading(*min);
ReadingRangeConfigs& min_configs = label_to_reading_range_configs[label];
*min_configs.mutable_reading_range_configs()->Add() = min_range;
}
}
return label_to_reading_range_configs;
}
absl::StatusOr<I2cCommonConfig> EntityConfigJsonImpl::ParseI2cCommonConfig(
const nlohmann::json& config) {
I2cCommonConfig i2c_config;
std::optional<uint64_t> bus =
GetValueAsUintFromStringOrInteger(config, "Bus");
if (!bus.has_value()) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: element does not have a Bus field: ", config.dump()));
}
i2c_config.set_bus(*bus);
const std::string* address = GetValueAsString(config, "Address");
if (address == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: element does not have an Address field");
}
uint64_t address_uint64;
if (!absl::SimpleHexAtoi(*address, &address_uint64)) {
return absl::InvalidArgumentError(
"Invalid config: Address field is not a valid hex number: ");
}
i2c_config.set_address(address_uint64);
return i2c_config;
}
absl::StatusOr<std::vector<std::string>> EntityConfigJsonImpl::ParseLabels(
const nlohmann::json& config) {
const nlohmann::json::array_t* labels = GetValueAsArray(config, "Labels");
if (labels == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Labels field is not an array");
}
std::vector<std::string> labels_vector;
for (const auto& label : *labels) {
const std::string* label_str = label.get_ptr<const std::string*>();
if (label_str == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: label element is not a string");
}
labels_vector.push_back(*label_str);
}
return labels_vector;
}
absl::StatusOr<ThresholdType> EntityConfigJsonImpl::ParseThresholdType(
std::string_view name) {
constexpr absl::string_view kUpperCritical = "Upper Critical";
constexpr absl::string_view kUpperNonCritical = "Upper Non Critical";
constexpr absl::string_view kLowerCritical = "Lower Critical";
constexpr absl::string_view kLowerNonCritical = "Lower Non Critical";
if (absl::EqualsIgnoreCase(name, kUpperCritical)) {
return THRESHOLD_TYPE_UPPER_CRITICAL;
}
if (absl::EqualsIgnoreCase(name, kUpperNonCritical)) {
return THRESHOLD_TYPE_UPPER_NON_CRITICAL;
}
if (absl::EqualsIgnoreCase(name, kLowerCritical)) {
return THRESHOLD_TYPE_LOWER_CRITICAL;
}
if (absl::EqualsIgnoreCase(name, kLowerNonCritical)) {
return THRESHOLD_TYPE_LOWER_NON_CRITICAL;
}
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Type field is not a valid threshold type: ", name));
}
absl::StatusOr<std::unordered_map<std::string, ThresholdConfigs>>
EntityConfigJsonImpl::ParseThresholdConfigs(const nlohmann::json& config) {
std::unordered_map<std::string, ThresholdConfigs> label_to_threshold_configs;
if (!config.contains("Thresholds")) {
return label_to_threshold_configs;
}
const nlohmann::json::array_t* threshold_configs =
GetValueAsArray(config, "Thresholds");
if (threshold_configs == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Thresholds field is not an array");
}
for (const auto& threshold_element : *threshold_configs) {
const std::string* name = GetValueAsString(threshold_element, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
ECCLESIA_ASSIGN_OR_RETURN(ThresholdType threshold_type,
ParseThresholdType(*name));
ThresholdConfig threshold_config;
threshold_config.set_type(threshold_type);
std::optional<double> value =
GetValueAsDoubleFromFloatOrInteger(threshold_element, "Value");
if (!value.has_value()) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid threshold config: Value field is not a double: config is ",
threshold_element.dump()));
}
threshold_config.set_value(*value);
if (threshold_element.contains("Label")) {
const std::string* label = GetValueAsString(threshold_element, "Label");
if (label == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid threshold config: Label field is not a string",
threshold_element.dump()));
}
*label_to_threshold_configs[*label].add_threshold_configs() =
threshold_config;
} else {
*label_to_threshold_configs[""].add_threshold_configs() =
threshold_config;
}
}
return label_to_threshold_configs;
}
absl::StatusOr<absl::flat_hash_map<std::string, ThresholdConfigs>>
EntityConfigJsonImpl::ParseThresholdConfigsForHwmonTemp(
const nlohmann::json& config, const std::vector<std::string>& labels) {
absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs;
if (!config.contains("Thresholds")) {
return label_to_threshold_configs;
}
const nlohmann::json::array_t* threshold_configs =
GetValueAsArray(config, "Thresholds");
if (threshold_configs == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Thresholds field is not an array");
}
for (const auto& threshold_element : *threshold_configs) {
const std::string* name = GetValueAsString(threshold_element, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
ECCLESIA_ASSIGN_OR_RETURN(ThresholdType threshold_type,
ParseThresholdType(*name));
ThresholdConfig threshold_config;
threshold_config.set_type(threshold_type);
std::optional<double> value =
GetValueAsDoubleFromFloatOrInteger(threshold_element, "Value");
if (!value.has_value()) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid threshold config: Value field is not a double: config is ",
threshold_element.dump()));
}
threshold_config.set_value(*value);
for (const auto& label : labels) {
*label_to_threshold_configs[label].add_threshold_configs() =
threshold_config;
}
}
return label_to_threshold_configs;
}
absl::StatusOr<std::unordered_map<std::string, std::string>>
EntityConfigJsonImpl::ParseLabelToName(const nlohmann::json& config,
absl::Span<const std::string> labels,
bool ignore_zero_index_name) {
std::unordered_map<std::string, std::string> label_to_name;
for (std::size_t i = 0; i < labels.size(); ++i) {
const std::string& label = labels[i];
std::string key = absl::Substitute("$0_Name", label);
const std::string* name_str = GetValueAsString(config, key);
if (name_str == nullptr && !ignore_zero_index_name) {
// Try the other schema if $0_Name is not found.
key = (i == 0) ? "Name" : absl::Substitute("Name$0", i);
name_str = GetValueAsString(config, key);
if (name_str == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid config: Name field is not a string or missing: ",
config.dump()));
}
}
if (name_str == nullptr) {
label_to_name[label] = "";
} else {
label_to_name[label] = *name_str;
}
}
return label_to_name;
}
// Configure refresh interval
template <typename T>
absl::Status ParseCommonEntityConfig(const nlohmann::json& config, T& sensor) {
const auto* refresh_interval =
GetValueAsUint(config, kRefreshIntervalKeyword);
if (refresh_interval == nullptr) {
return absl::OkStatus();
}
auto proto_duration = ecclesia::AbslDurationToProtoDuration(
absl::Milliseconds(*refresh_interval));
if (!proto_duration.ok()) {
return proto_duration.status();
}
*sensor.mutable_entity_common_config()->mutable_refresh_interval() =
*proto_duration;
if (const auto* queue_size = GetValueAsUint(config, kQueueSizeKeyword);
queue_size != nullptr) {
sensor.mutable_entity_common_config()->set_queue_size(*queue_size);
}
return absl::OkStatus();
}
absl::StatusOr<HwmonTempSensorConfig>
EntityConfigJsonImpl::ParseHwmonTempSensorConfig(HwmonTempSensorType type,
const nlohmann::json& config) {
if (type == HWMON_TEMP_SENSOR_TYPE0_UNKNOWN) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a supported HWMon temperature "
"sensor type");
}
HwmonTempSensorConfig hwmon_temp_sensor_config;
hwmon_temp_sensor_config.set_type(type);
ECCLESIA_ASSIGN_OR_RETURN(
*hwmon_temp_sensor_config.mutable_i2c_common_config(),
ParseI2cCommonConfig(config));
ECCLESIA_RETURN_IF_ERROR(
ParseCommonEntityConfig(config, hwmon_temp_sensor_config));
// Default reading range for hwmon temperature sensors.
// From:
// https://github.com/openbmc/dbus-sensors/blob/af1724b84b7558037c665d2106ec44d7362aca6b/src/hwmon-temp/HwmonTempMain.cpp#L62
constexpr double kDefaultTempMinReading = -128.0;
constexpr double kDefaultTempMaxReading = 127.0;
ReadingRangeConfig max;
max.set_type(READING_RANGE_TYPE_MAX);
max.set_reading(kDefaultTempMaxReading);
ReadingRangeConfig min;
min.set_type(READING_RANGE_TYPE_MIN);
min.set_reading(kDefaultTempMinReading);
ReadingRangeConfigs reading_range_configs;
*reading_range_configs.add_reading_range_configs() = max;
*reading_range_configs.add_reading_range_configs() = min;
std::vector<std::string> labels;
const nlohmann::json::array_t* labels_array =
GetValueAsArray(config, "Labels");
// If the labels array is not present, we use the default label "temp$i".
if (labels_array == nullptr) {
for (size_t i = 0;; i++) {
std::string key = "Name";
if (i > 0) key += std::to_string(i);
const std::string* name_str = GetValueAsString(config, key);
if (name_str == nullptr) {
break;
}
std::string label = absl::Substitute("temp$0", i + 1);
(*hwmon_temp_sensor_config.mutable_label_to_name())[label] = *name_str;
(*hwmon_temp_sensor_config.mutable_label_to_reading_ranges())[label] =
reading_range_configs;
labels.push_back(label);
}
} else {
// Otherwise, we use the labels array and the labels will be checked against
// the labels files.
for (const auto& label : *labels_array) {
const std::string* label_str = label.get_ptr<const std::string*>();
if (label_str == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Labels field is not a string array");
}
labels.push_back(*label_str);
(*hwmon_temp_sensor_config
.mutable_label_to_reading_ranges())[*label_str] =
reading_range_configs;
}
std::unordered_map<std::string, std::string> label_to_name;
ECCLESIA_ASSIGN_OR_RETURN(label_to_name, ParseLabelToName(config, labels));
hwmon_temp_sensor_config.mutable_label_to_name()->insert(
label_to_name.begin(), label_to_name.end());
}
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
if (related_item.has_value()) {
*hwmon_temp_sensor_config.mutable_entity_common_config()
->mutable_related_item() = *related_item;
}
absl::flat_hash_map<std::string, ThresholdConfigs> label_to_threshold_configs;
ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs,
ParseThresholdConfigsForHwmonTemp(config, labels));
hwmon_temp_sensor_config.mutable_label_to_thresholds()->insert(
label_to_threshold_configs.begin(), label_to_threshold_configs.end());
return hwmon_temp_sensor_config;
}
absl::StatusOr<FanControllerConfig>
EntityConfigJsonImpl::ParseFanControllerConfig(FanControllerType type,
const nlohmann::json& config) {
if (type == FAN_CONTROLLER_TYPE_UNKNOWN) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a supported Fan controller type");
}
FanControllerConfig fan_controller_config;
fan_controller_config.set_type(type);
ECCLESIA_ASSIGN_OR_RETURN(*fan_controller_config.mutable_i2c_common_config(),
ParseI2cCommonConfig(config));
return fan_controller_config;
}
absl::StatusOr<PsuSensorConfig> EntityConfigJsonImpl::ParsePsuSensorConfig(
PsuSensorType type, const nlohmann::json& config) {
if (type == PSU_SENSOR_TYPE0_UNKNOWN) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a supported PSU sensor type");
}
PsuSensorConfig psu_sensor_config;
psu_sensor_config.set_type(type);
const std::string* name = GetValueAsString(config, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
psu_sensor_config.set_name(*name);
ECCLESIA_ASSIGN_OR_RETURN(*psu_sensor_config.mutable_i2c_common_config(),
ParseI2cCommonConfig(config));
ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, psu_sensor_config));
std::vector<std::string> labels;
ECCLESIA_ASSIGN_OR_RETURN(labels, ParseLabels(config));
std::unordered_map<std::string, std::string> label_to_name;
ECCLESIA_ASSIGN_OR_RETURN(label_to_name,
ParseLabelToName(config, labels, true));
psu_sensor_config.mutable_label_to_name()->insert(label_to_name.begin(),
label_to_name.end());
std::unordered_map<std::string, ThresholdConfigs> label_to_threshold_configs;
ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs,
ParseThresholdConfigs(config));
psu_sensor_config.mutable_label_to_thresholds()->insert(
label_to_threshold_configs.begin(), label_to_threshold_configs.end());
std::unordered_map<std::string, ReadingRangeConfigs> label_to_reading_range =
ParseReadingRangeConfigs(config, labels);
psu_sensor_config.mutable_label_to_reading_ranges()->insert(
label_to_reading_range.begin(), label_to_reading_range.end());
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
if (related_item.has_value()) {
*psu_sensor_config.mutable_entity_common_config()->mutable_related_item() =
*related_item;
}
return psu_sensor_config;
}
absl::StatusOr<FanPwmConfig> EntityConfigJsonImpl::ParseFanPwmConfig(
FanPwmType type, const nlohmann::json& config) {
if (type == PWM_SENSOR_TYPE_UNKNOWN) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a supported Fan PWM type");
}
FanPwmConfig fan_pwm_config;
fan_pwm_config.set_type(type);
ECCLESIA_ASSIGN_OR_RETURN(*fan_pwm_config.mutable_i2c_common_config(),
ParseI2cCommonConfig(config));
// Fan PWM has fixed max and min reading ranges.
ReadingRangeConfig max;
max.set_type(READING_RANGE_TYPE_MAX);
max.set_reading(100);
*fan_pwm_config.mutable_reading_ranges()->add_reading_range_configs() = max;
ReadingRangeConfig min;
min.set_type(READING_RANGE_TYPE_MIN);
min.set_reading(0);
*fan_pwm_config.mutable_reading_ranges()->add_reading_range_configs() = min;
const nlohmann::json* connector = GetValueAsJson(config, "Connector");
if (connector == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Connector field is not a JSON");
}
const std::string* name = GetValueAsString(*connector, "PwmName");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Connector PwmName field is not a string");
}
fan_pwm_config.set_name(*name);
const uint64_t* index = GetValueAsUint(*connector, "Pwm");
if (index == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Connector Index field is not a uint64_t");
}
fan_pwm_config.set_index(static_cast<int32_t>(*index + 1));
ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, fan_pwm_config));
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
if (related_item.has_value()) {
*fan_pwm_config.mutable_entity_common_config()->mutable_related_item() =
*related_item;
}
return fan_pwm_config;
}
absl::StatusOr<FanTachConfig> EntityConfigJsonImpl::ParseFanTachConfig(
FanTachType type, const nlohmann::json& config) {
if (type == TACH_SENSOR_TYPE_UNKNOWN) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a supported Fan PWM type");
}
FanTachConfig fan_tach_config;
fan_tach_config.set_type(type);
ECCLESIA_ASSIGN_OR_RETURN(*fan_tach_config.mutable_i2c_common_config(),
ParseI2cCommonConfig(config));
constexpr int kDefaultMaxFanReading = 25000;
constexpr int kDefaultMinFanReading = 0;
ReadingRangeConfigs reading_range_configs = ParseReadingRangeConfigs(
config, kDefaultMinFanReading, kDefaultMaxFanReading);
*fan_tach_config.mutable_reading_ranges() = reading_range_configs;
const std::string* name = GetValueAsString(config, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
fan_tach_config.set_name(*name);
const uint64_t* index = GetValueAsUint(config, "Index");
if (index == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Index field is not a uint64_t");
}
fan_tach_config.set_index(static_cast<int32_t>(*index + 1));
std::unordered_map<std::string, ThresholdConfigs> label_to_threshold_configs;
ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs,
ParseThresholdConfigs(config));
*fan_tach_config.mutable_thresholds() = label_to_threshold_configs[""];
ECCLESIA_RETURN_IF_ERROR(ParseCommonEntityConfig(config, fan_tach_config));
std::optional<RelatedItem> related_item = ParseRelatedItem(config);
if (related_item.has_value()) {
*fan_tach_config.mutable_entity_common_config()->mutable_related_item() =
*related_item;
}
return fan_tach_config;
}
absl::StatusOr<SharedMemSensorConfig>
EntityConfigJsonImpl::ParseSharedMemSensorConfig(SharedMemSensorType type,
const nlohmann::json& config) {
if (type == SHARED_MEM_SENSOR_TYPE_TYPE0_UNKNOWN) {
return absl::InvalidArgumentError(
"Invalid config: Type field is not a supported Shared Mem Sensor type");
}
SharedMemSensorConfig shared_mem_sensor_config;
shared_mem_sensor_config.set_type(type);
const std::string* name = GetValueAsString(config, "Name");
if (name == nullptr) {
return absl::InvalidArgumentError(
"Invalid config: Name field is not a string or missing");
}
shared_mem_sensor_config.set_name(*name);
shared_mem_sensor_config.set_unit(ParseSensorUnit(config));
std::unordered_map<std::string, ThresholdConfigs> label_to_threshold_configs;
ECCLESIA_ASSIGN_OR_RETURN(label_to_threshold_configs,
ParseThresholdConfigs(config));
*shared_mem_sensor_config.mutable_thresholds() =
label_to_threshold_configs[""];
ECCLESIA_RETURN_IF_ERROR(
ParseCommonEntityConfig(config, shared_mem_sensor_config));
return shared_mem_sensor_config;
}
absl::Status EntityConfigJsonImpl::CreateAssociationsBetweenTopologyConfigNodes(
TopologyConfig& topology_config, FruTable& fru_table,
absl::flat_hash_set<absl::string_view> expected_nodes_traversed) {
// Find root node in TopologyConfig.
std::string root_chassis_location_code;
for (const auto& [config_name, topology_config_node] :
topology_config.topology_config_nodes()) {
// There are multiple inconsistent root chassis location codes found in the
// configs. Stop the topology buildup with an error.
if (!root_chassis_location_code.empty() &&
topology_config_node.has_root_chassis_location_code()) {
return absl::InternalError(absl::StrFormat(
"Invalid config: Multiple root chassis location "
"codes found! Previous: %s. Current: %s",
root_chassis_location_code,
topology_config_node.root_chassis_location_code()));
}
if (topology_config_node.has_root_chassis_location_code()) {
root_chassis_location_code =
topology_config_node.root_chassis_location_code();
}
if (!topology_config_node.has_upstream_port_config() &&
!topology_config_node.has_upstream_cable_port_config()) {
if (topology_config.has_root_node_name()) {
return absl::InternalError("Invalid config: Multiple root nodes found");
}
topology_config.set_root_node_name(config_name);
}
}
if (!topology_config.has_root_node_name()) {
return absl::InternalError("Invalid config: No root node found");
}
absl::flat_hash_map<std::string, std::string>
upstream_cable_port_name_to_config_name;
// Map of port name to config name.
absl::flat_hash_map<std::string, std::string>
upstream_port_name_to_config_name;
for (const auto& [unused, topology_config_node] :
topology_config.topology_config_nodes()) {
if (!topology_config_node.upstream_cable_port_config()
.port_name()
.empty()) {
upstream_cable_port_name_to_config_name
[topology_config_node.upstream_cable_port_config().port_name()] =
topology_config_node.name();
}
upstream_port_name_to_config_name
[topology_config_node.upstream_port_config().port_name()] =
topology_config_node.name();
}
// Derive devpath for each topology config node.
// Not checking for presence of root node since it is already checked above.
TopologyConfigNode& root_node =
topology_config.mutable_topology_config_nodes()->at(
topology_config.root_node_name());
root_node.mutable_location_context()->set_devpath(absl::StrCat(
"/", (root_chassis_location_code.empty()
? "phys"
: root_node.root_chassis_location_code().c_str())));
LOG(INFO) << "Final root devpath: " << root_node.location_context().devpath();
std::queue<TopologyConfigNode*> node_queue;
node_queue.push(&root_node);
absl::flat_hash_set<absl::string_view> unexpected_nodes_traversed;
while (!node_queue.empty()) {
TopologyConfigNode* current_node = node_queue.front();
node_queue.pop();
if (expected_nodes_traversed.contains(current_node->name())) {
expected_nodes_traversed.erase(current_node->name());
} else {
unexpected_nodes_traversed.insert(current_node->name());
}
auto find_current_fru = fru_table.mutable_key_to_fru()->find(
current_node->fru_info().fru_key());
if (find_current_fru == fru_table.key_to_fru().end()) {
LOG(WARNING) << "FRU not found for key while setting devpath: "
<< current_node->fru_info().fru_key()
<< ". This is normally not expected. Check debug endpoint "
"and reproduce the issue.";
continue;
}
Fru& current_fru = find_current_fru->second;
for (const auto& [port_name, port_config] : current_node->port_configs()) {
auto find_downstream_node_name =
upstream_port_name_to_config_name.find(port_name);
if (find_downstream_node_name ==
upstream_port_name_to_config_name.end()) {
continue;
}
absl::string_view downstream_node_name =
find_downstream_node_name->second;
// Downstream node is always present in the topology config so will not
// check for presence.
TopologyConfigNode& downstream_node =
topology_config.mutable_topology_config_nodes()->at(
downstream_node_name);
// If node is Sub-Fru then we add :device to the devpath and then append
// the port label.
if (downstream_node.fru_info().is_sub_fru()) {
downstream_node.mutable_location_context()->set_devpath(
absl::StrCat(current_node->mutable_location_context()->devpath(),
":device:", port_config.port_label()));
} else {
downstream_node.mutable_location_context()->set_devpath(
absl::StrCat(current_node->mutable_location_context()->devpath(),
"/", port_config.port_label()));
}
// Downstream node key is always present in the FRU table so will not
// check for presence.
Fru& downstream_fru = fru_table.mutable_key_to_fru()->at(
downstream_node.fru_info().fru_key());
downstream_node.set_parent_resource_id(current_node->name());
if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) {
current_node->add_children_chassis_ids(downstream_node.name());
}
node_queue.push(&downstream_node);
}
for (const auto& [port_name, port_config] :
current_node->cable_port_configs()) {
auto find_downstream_node_name =
upstream_cable_port_name_to_config_name.find(port_name);
if (find_downstream_node_name ==
upstream_cable_port_name_to_config_name.end()) {
continue;
}
absl::string_view downstream_node_name =
find_downstream_node_name->second;
TopologyConfigNode& downstream_node =
topology_config.mutable_topology_config_nodes()->at(
downstream_node_name);
auto find_cable_fru =
topology_config.mutable_topology_config_nodes()->find(
port_config.port_label());
if (find_cable_fru ==
topology_config.mutable_topology_config_nodes()->end()) {
return absl::InternalError(absl::StrCat(
"Invalid config: Cable config name does not match cable connection "
"CableId: ",
port_config.port_label(), " cannot populate this cable."));
}
TopologyConfigNode& cable_topology_node = find_cable_fru->second;
if (current_fru.attributes().resource_type() != RESOURCE_TYPE_CABLE) {
downstream_node.mutable_location_context()->set_devpath(
absl::StrCat(current_node->mutable_location_context()->devpath(),
"/", "DOWNLINK"));
node_queue.push(&downstream_node);
cable_topology_node.set_parent_resource_id(current_node->name());
current_node->add_children_cable_ids(cable_topology_node.name());
} else {
downstream_node.add_children_cable_ids(cable_topology_node.name());
}
}
}
if (!expected_nodes_traversed.empty() ||
!unexpected_nodes_traversed.empty()) {
return absl::InternalError(absl::Substitute(
"The following FRUs are not connected to the topology: $0, which "
"means the config in this machine is not fully tested by tlBMC. The "
"following FRUs were found in the topology, but not expected: $1",
absl::StrJoin(expected_nodes_traversed, ", "),
absl::StrJoin(unexpected_nodes_traversed, ", ")));
}
return absl::OkStatus();
}
absl::StatusOr<const TopologyConfigNode*> EntityConfigJsonImpl::GetFruTopology(
absl::string_view fru_key) const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
const auto fru_config_it = topology_config->fru_configs().find(fru_key);
if (fru_config_it == topology_config->fru_configs().end()) {
return absl::NotFoundError(absl::StrCat("FRU key not found: ", fru_key));
}
return &(topology_config->topology_config_nodes().at(fru_config_it->second));
}
absl::StatusOr<const TopologyConfigNode*>
EntityConfigJsonImpl::GetFruTopologyByConfig(
absl::string_view config_name) const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
const auto fru_config_it =
topology_config->topology_config_nodes().find(config_name);
if (fru_config_it == topology_config->topology_config_nodes().end()) {
return absl::NotFoundError(
absl::StrCat("Config name not found: ", config_name));
}
return &(fru_config_it->second);
}
absl::StatusOr<const TopologyConfig*> EntityConfigJsonImpl::GetTopologyConfig()
const {
ECCLESIA_RETURN_IF_ERROR(GetParsedStatus());
return &data_store_.mutable_data.Get()->topology_config;
}
absl::StatusOr<std::vector<std::string>>
EntityConfigJsonImpl::GetAllConfigNames() const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
std::vector<std::string> config_names;
for (const auto& [config_name, topology_config_node] :
topology_config->topology_config_nodes()) {
if (!topology_config_node.not_owned_by_tlbmc()) {
config_names.push_back(config_name);
}
}
return config_names;
}
absl::StatusOr<std::string> EntityConfigJsonImpl::GetConfigNameByFruKey(
absl::string_view fru_key) const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
auto fru_config_it = topology_config->fru_configs().find(fru_key);
if (fru_config_it == topology_config->fru_configs().end()) {
return absl::NotFoundError(absl::StrCat("FRU key not found: ", fru_key));
}
return fru_config_it->second;
}
absl::StatusOr<std::string> EntityConfigJsonImpl::GetFruKeyByConfigName(
absl::string_view config_name) const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
auto fru_config_it =
topology_config->topology_config_nodes().find(config_name);
if (fru_config_it == topology_config->topology_config_nodes().end()) {
return absl::NotFoundError(
absl::StrCat("Config name not found: ", config_name));
}
return fru_config_it->second.fru_info().fru_key();
}
absl::StatusOr<const Fru*> EntityConfigJsonImpl::GetFru(
absl::string_view key) const {
ECCLESIA_ASSIGN_OR_RETURN(const FruTable* fru_table, GetAllFrus());
auto it = fru_table->key_to_fru().find(key);
if (it == fru_table->key_to_fru().end()) {
return absl::NotFoundError(absl::StrCat("FRU key not found: ", key));
}
return &it->second;
}
absl::StatusOr<const FruTable*> EntityConfigJsonImpl::GetAllFrus() const {
ECCLESIA_RETURN_IF_ERROR(GetParsedStatus());
return &data_store_.mutable_data.Get()->fru_table;
}
absl::StatusOr<std::string> EntityConfigJsonImpl::GetFruDevpath(
absl::string_view fru_key) const {
ECCLESIA_ASSIGN_OR_RETURN(const TopologyConfig* topology_config,
GetTopologyConfig());
auto fru_config_it = topology_config->fru_configs().find(fru_key);
if (fru_config_it == topology_config->fru_configs().end()) {
return absl::NotFoundError(absl::StrCat("FRU key not found: ", fru_key));
}
return topology_config->topology_config_nodes()
.at(fru_config_it->second)
.location_context()
.devpath();
}
nlohmann::json EntityConfigJsonImpl::ToJson() const {
nlohmann::json json;
json["HwmonTempSensorConfigs"] = nlohmann::json::array();
for (const auto& hwmon_temp_sensor_config :
data_store_.immutable_data.hwmon_temp_sensor_configs) {
json["HwmonTempSensorConfigs"].push_back(
ProtoToJson(hwmon_temp_sensor_config));
}
json["PsuSensorConfigs"] = nlohmann::json::array();
for (const auto& psu_sensor_config :
data_store_.immutable_data.psu_sensor_configs) {
json["PsuSensorConfigs"].push_back(ProtoToJson(psu_sensor_config));
}
json["FanControllerConfigs"] = nlohmann::json::array();
for (const auto& fan_controller_config :
data_store_.immutable_data.fan_controller_configs) {
json["FanControllerConfigs"].push_back(ProtoToJson(fan_controller_config));
}
json["FanPwmConfigs"] = nlohmann::json::array();
for (const auto& fan_pwm_config :
data_store_.immutable_data.fan_pwm_configs) {
json["FanPwmConfigs"].push_back(ProtoToJson(fan_pwm_config));
}
json["FanTachConfigs"] = nlohmann::json::array();
for (const auto& fan_tach_config :
data_store_.immutable_data.fan_tach_configs) {
json["FanTachConfigs"].push_back(ProtoToJson(fan_tach_config));
}
json["SharedMemSensorConfigs"] = nlohmann::json::array();
for (const auto& shared_mem_sensor_config :
data_store_.immutable_data.shared_mem_sensor_configs) {
json["SharedMemSensorConfigs"].push_back(
ProtoToJson(shared_mem_sensor_config));
}
const EntityConfigJsonImplMutableData* mutable_data =
data_store_.mutable_data.Get();
if (mutable_data != nullptr) {
json["TopologyConfig"] = ProtoToJson(mutable_data->topology_config);
json["FruTable"] = ProtoToJson(mutable_data->fru_table);
}
json["CurrentStatus"] = mutable_data->parsed_status.ToString();
return json;
}
EntityConfigJsonImpl::EntityConfigJsonImpl(
std::vector<nlohmann::json>&& config_list, const RawFruTable& fru_table,
size_t ad_hoc_fru_count)
: immutable_config_list_(std::move(config_list)),
data_store_(PackDataIntoStore(
ReloadConfig(fru_table, ad_hoc_fru_count, ReloadType::kReloadAll))) {}
absl::StatusOr<std::unique_ptr<EntityConfig>> EntityConfigJsonImpl::Create(
std::vector<nlohmann::json>&& config_list, const RawFruTable& fru_table,
size_t ad_hoc_fru_count) {
auto entity_config = absl::WrapUnique(new EntityConfigJsonImpl(
std::move(config_list), fru_table, ad_hoc_fru_count));
ECCLESIA_RETURN_IF_ERROR(entity_config->GetParsedStatus());
return entity_config;
}
absl::StatusOr<std::vector<nlohmann::json>>
EntityConfigJsonImpl::ParseJsonFilesIntoConfigList(
absl::string_view config_location) {
std::vector<nlohmann::json> config_list;
if (!std::filesystem::exists(config_location)) {
return absl::NotFoundError(
absl::StrCat("Config location not found: ", config_location));
}
for (const auto& entry :
std::filesystem::directory_iterator(config_location)) {
if (entry.is_regular_file() && entry.path().extension() == ".json") {
std::string json_file_path = entry.path().string();
std::ifstream json_file(json_file_path);
if (!json_file.is_open()) {
return absl::InternalError(
absl::StrCat("Failed to open file: ", json_file_path));
}
nlohmann::json parsed_config =
nlohmann::json::parse(json_file, nullptr, /*allow_exceptions=*/false,
/*ignore_comments=*/true);
if (parsed_config.is_discarded()) {
return absl::InvalidArgumentError(
absl::StrCat(json_file_path, " is illformed."));
}
nlohmann::json::array_t config_as_array;
if (parsed_config.is_array()) {
config_as_array = parsed_config.get<nlohmann::json::array_t>();
} else {
config_as_array.push_back(parsed_config);
}
for (const auto& config : config_as_array) {
config_list.push_back(config);
}
}
}
return config_list;
}
void EntityConfigJsonImpl::SetSmartRouter(RouterInterface* smart_router) {
smart_router_ = smart_router;
}
absl::Status EntityConfigJsonImpl::GetParsedStatus() const {
return data_store_.mutable_data.Get()->parsed_status;
}
EntityConfigJsonImplDataStore EntityConfigJsonImpl::PackDataIntoStore(
EntityConfigJsonImplData&& data) {
return EntityConfigJsonImplDataStore{
.immutable_data = std::move(data.immutable_data),
.mutable_data = SimpleRcu<EntityConfigJsonImplMutableData>(
std::move(data.mutable_data), data.max_updates_to_mutable_data)};
}
} // namespace milotic_tlbmc