Update topology config check
A more accurate measure of if we have correctly created links to all detected Frus is to ensure the Frus reachable from root node in the topology config matches the detected Frus in the FruTable and all sub-frus. We create a set of expected Fru objects to be traversed and ensure that all are reachable from the root node in the TopologyConfig.
This avoids store being created when there are nodes without Upstream/Downstream connections and are therefore not part of the topology.
This also simplifies the check implemented in cl/744087916 and cl/744087916 into one check.
#tlbmc
PiperOrigin-RevId: 744903038
Change-Id: I6ea25dec731c46f208ea97fa21cf62cfc0570553
diff --git a/tlbmc/configs/entity_config_json_impl.cc b/tlbmc/configs/entity_config_json_impl.cc
index 159319c..eecc5a8 100644
--- a/tlbmc/configs/entity_config_json_impl.cc
+++ b/tlbmc/configs/entity_config_json_impl.cc
@@ -18,6 +18,7 @@
#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"
@@ -89,146 +90,6 @@
constexpr absl::string_view kRelatedItemKeyword = "RelatedItem";
constexpr absl::string_view kResourceTypeKeyword = "ResourceType";
-absl::Status CreateAssociationsBetweenTopologyConfigNodes(
- TopologyConfig& topology_config, FruTable& fru_table) {
- // Find root node in TopologyConfig.
- for (const auto& [config_name, topology_config_node] :
- topology_config.topology_config_nodes()) {
- 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.set_devpath("/phys");
- int nodes_found = 0;
- std::queue<TopologyConfigNode*> node_queue;
- node_queue.push(&root_node);
- while (!node_queue.empty()) {
- TopologyConfigNode* current_node = node_queue.front();
- node_queue.pop();
- nodes_found++;
- 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;
- current_fru.mutable_attributes()->mutable_location_context()->set_devpath(
- current_node->devpath());
- 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.set_devpath(absl::StrCat(
- current_node->devpath(), ":device:", port_config.port_label()));
- } else {
- downstream_node.set_devpath(absl::StrCat(current_node->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_fru.set_parent_fru_key(current_fru.attributes().key());
- if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) {
- current_fru.add_children_chassis_keys(
- downstream_node.fru_info().fru_key());
- }
- 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 =
- fru_table.mutable_key_to_fru()->find(port_config.port_label());
- if (find_cable_fru == fru_table.key_to_fru().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."));
- }
- Fru& cable_fru = find_cable_fru->second;
- if (current_fru.attributes().resource_type() != RESOURCE_TYPE_CABLE) {
- downstream_node.set_devpath(
- absl::StrCat(current_node->devpath(), "/", "DOWNLINK"));
- node_queue.push(&downstream_node);
- cable_fru.set_parent_fru_key(current_fru.attributes().key());
- current_fru.add_children_cable_ids(cable_fru.attributes().key());
- } else {
- fru_table.mutable_key_to_fru()
- ->at(downstream_node.fru_info().fru_key())
- .add_children_cable_ids(cable_fru.attributes().key());
- }
- }
- }
- if (nodes_found != topology_config.topology_config_nodes_size()) {
- return absl::InternalError(
- "Invalid topology: Not all topology config nodes are connected when "
- "creating topology.");
- }
- return absl::OkStatus();
-}
-
absl::Status ParseProcessorConfig(const nlohmann::json& config, Fru& fru,
FruTable& fru_table,
TopologyConfig& topology_config) {
@@ -1188,9 +1049,29 @@
*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()) {
+ if (!mutable_data.topology_config.mutable_fru_configs()->contains(
+ fru_key)) {
+ 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};
+ }
+ fru_keys_with_sub_frus.insert(
+ mutable_data.topology_config.mutable_fru_configs()->at(fru_key));
+ }
+ for (const auto& [fru_key, sub_fru_config_name] : fru_key_to_sub_fru_config) {
+ fru_keys_with_sub_frus.insert(sub_fru_config_name);
+ }
+
absl::Status create_associations_status =
- CreateAssociationsBetweenTopologyConfigNodes(mutable_data.topology_config,
- mutable_data.fru_table);
+ 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{
@@ -1202,20 +1083,6 @@
// Sort the children of each Fru
SortChildrenResourceIds(mutable_data.fru_table);
- // Sanity check that all Raw Frus exist in EM config.
- std::vector<std::string> missing_raw_fru_keys;
- for (const auto& [raw_fru_key, raw_fru] : fru_table.key_to_raw_fru()) {
- if (!mutable_data.topology_config.fru_configs().contains(raw_fru_key)) {
- missing_raw_fru_keys.push_back(raw_fru_key);
- }
- }
-
- if (!missing_raw_fru_keys.empty()) {
- mutable_data.parsed_status = absl::InternalError(
- absl::StrCat("Raw FRU keys not found in EM Topology: ",
- absl::StrJoin(missing_raw_fru_keys, ", ")));
- }
-
return EntityConfigJsonImplData{
.immutable_data = std::move(immutable_data),
.mutable_data = std::move(mutable_data),
@@ -1734,6 +1601,156 @@
return fan_tach_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.
+ for (const auto& [config_name, topology_config_node] :
+ topology_config.topology_config_nodes()) {
+ 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.set_devpath("/phys");
+ 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;
+ current_fru.mutable_attributes()->mutable_location_context()->set_devpath(
+ current_node->devpath());
+ 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.set_devpath(absl::StrCat(
+ current_node->devpath(), ":device:", port_config.port_label()));
+ } else {
+ downstream_node.set_devpath(absl::StrCat(current_node->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_fru.set_parent_fru_key(current_fru.attributes().key());
+ if (downstream_fru.attributes().resource_type() == RESOURCE_TYPE_BOARD) {
+ current_fru.add_children_chassis_keys(
+ downstream_node.fru_info().fru_key());
+ }
+ 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 =
+ fru_table.mutable_key_to_fru()->find(port_config.port_label());
+ if (find_cable_fru == fru_table.key_to_fru().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."));
+ }
+ Fru& cable_fru = find_cable_fru->second;
+ if (current_fru.attributes().resource_type() != RESOURCE_TYPE_CABLE) {
+ downstream_node.set_devpath(
+ absl::StrCat(current_node->devpath(), "/", "DOWNLINK"));
+ node_queue.push(&downstream_node);
+ cable_fru.set_parent_fru_key(current_fru.attributes().key());
+ current_fru.add_children_cable_ids(cable_fru.attributes().key());
+ } else {
+ fru_table.mutable_key_to_fru()
+ ->at(downstream_node.fru_info().fru_key())
+ .add_children_cable_ids(cable_fru.attributes().key());
+ }
+ }
+ }
+
+ 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,
diff --git a/tlbmc/configs/entity_config_json_impl.h b/tlbmc/configs/entity_config_json_impl.h
index 95bcd54..e8055aa 100644
--- a/tlbmc/configs/entity_config_json_impl.h
+++ b/tlbmc/configs/entity_config_json_impl.h
@@ -9,6 +9,7 @@
#include <tuple>
#include <utility>
+#include "absl/container/flat_hash_set.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "g3/macros.h"
@@ -277,6 +278,10 @@
static absl::StatusOr<FanTachConfig> ParseFanTachConfig(
FanTachType type, const nlohmann::json& config);
+ static absl::Status CreateAssociationsBetweenTopologyConfigNodes(
+ TopologyConfig& topology_config, FruTable& fru_table,
+ absl::flat_hash_set<absl::string_view> expected_nodes_traversed);
+
absl::Status GetParsedStatus() const;
private: