Add probe FOUND() logic in tlbmc.
Support FOUND('<something>') syntax in tlbmc.
The process step:
1. Store the configs with FOUND probe
2. Process the other probe e.g. IpmiFru
3. Use topological sort to avoid recursion and find if the FOUND target exists or not.
#tlbmc
Startblock:
* has LGTM from tomtung
* and then
* add reviewer c-readability-approvers
* add reviewer nanzhou
PiperOrigin-RevId: 783370731
Change-Id: I32560a343dd08fc940df27ce60dc977f7c36b23f
diff --git a/tlbmc/configs/entity_config_json_impl.cc b/tlbmc/configs/entity_config_json_impl.cc
index bd0fcff..e452dc0 100644
--- a/tlbmc/configs/entity_config_json_impl.cc
+++ b/tlbmc/configs/entity_config_json_impl.cc
@@ -30,6 +30,7 @@
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
#include "absl/strings/substitute.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
@@ -454,9 +455,101 @@
RE2::FullMatch(find_fru_field->second, *probe_field));
}
+// Probes if the Ipmi Fru matches and updates the fru table and probed
+// config map.
+absl::Status ProbeFru(nlohmann::json& ipmi_fru_object,
+ const nlohmann::json& config, absl::string_view name,
+ FruTable& fru_table, ProbedConfigMap& probed_config_map) {
+ if (!ipmi_fru_object.is_array()) {
+ LOG(INFO) << "IpmiFru field is not an array, converting to array";
+ ipmi_fru_object = nlohmann::json::array({ipmi_fru_object});
+ }
+ for (const auto& ipmi_fru : ipmi_fru_object) {
+ // Get probe fields.
+ const auto* board_product_name =
+ GetValueAsString(ipmi_fru, "BOARD_PRODUCT_NAME");
+ const auto* product_product_name =
+ GetValueAsString(ipmi_fru, "PRODUCT_PRODUCT_NAME");
+ const auto* bus = GetValueAsUint(ipmi_fru, "BUS");
+ const auto* address = GetValueAsUint(ipmi_fru, "ADDRESS");
+ const auto* part_number = GetValueAsString(ipmi_fru, "BOARD_PART_NUMBER");
+ const auto* board_info_am2 = GetValueAsString(ipmi_fru, "BOARD_INFO_AM2");
+ const auto* product_info_am2 =
+ GetValueAsString(ipmi_fru, "PRODUCT_INFO_AM2");
+ if (board_product_name == nullptr && product_product_name == nullptr &&
+ bus == nullptr && address == nullptr && part_number == nullptr &&
+ board_info_am2 == nullptr && 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 board_info_am2_matches_or_null =
+ CheckProbeFieldMatch(board_info_am2, fru, "BOARD_INFO_AM2");
+ bool product_info_am2_matches_or_null =
+ CheckProbeFieldMatch(product_info_am2, fru, "PRODUCT_INFO_AM2");
+
+ // If the FRU doesn't match the probe, continue to the next FRU.
+ if (!i2c_info_matches_or_null || !board_product_name_matches_or_null ||
+ !product_product_name_matches_or_null ||
+ !part_number_matches_or_null || !board_info_am2_matches_or_null ||
+ !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 absl::OkStatus();
+}
+
+// Processes probe configs and returns a map of them.
absl::StatusOr<ProbedConfigMap> ProcessConfigs(
const std::vector<nlohmann::json>& config_list, FruTable& fru_table) {
ProbedConfigMap probed_config_map;
+ // Map to store the temporary config map for configs that have FOUND probe.
+ absl::flat_hash_map<std::string, std::vector<ProbedConfigData>>
+ pending_config_name_to_probed_configs;
for (const auto& config : config_list) {
absl::StatusOr<nlohmann::json> probe_result =
GetObject(config, kProbeKeyword);
@@ -468,28 +561,41 @@
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));
+ const std::string* probe_str = probe_result->get_ptr<const std::string*>();
+ if (probe_str != nullptr) {
+ std::string_view probe_str_view(*probe_str);
+ if (probe_str_view == "TRUE") {
+ 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;
}
- 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;
+ if (probe_str_view == "FALSE") {
+ continue;
+ }
+ // Store the config in the temporary map with key as FOUND probe
+ // and handle the config later.
+ if (absl::StartsWith(probe_str_view, "FOUND('") &&
+ absl::EndsWith(probe_str_view, "')")) {
+ absl::string_view found_config_name = absl::StripSuffix(
+ absl::StripPrefix(probe_str_view, "FOUND('"), "')");
+ ProbedConfigData probed_data;
+ probed_data.fru_keys.push_back(FruKey(name));
+ probed_data.config = config;
+ pending_config_name_to_probed_configs[found_config_name].push_back(
+ probed_data);
+ continue;
+ }
}
nlohmann::json probe = *probe_result;
// Perform probe.
@@ -500,85 +606,48 @@
"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});
+ absl::Status probe_fru_status =
+ ProbeFru(ipmi_fru_object, config, name, fru_table, probed_config_map);
+ if (!probe_fru_status.ok()) {
+ return probe_fru_status;
}
- for (const auto& ipmi_fru : ipmi_fru_object) {
- // Get probe fields.
- const auto* board_product_name =
- GetValueAsString(ipmi_fru, "BOARD_PRODUCT_NAME");
- const auto* product_product_name =
- GetValueAsString(ipmi_fru, "PRODUCT_PRODUCT_NAME");
- const auto* bus = GetValueAsUint(ipmi_fru, "BUS");
- const auto* address = GetValueAsUint(ipmi_fru, "ADDRESS");
- const auto* part_number = GetValueAsString(ipmi_fru, "BOARD_PART_NUMBER");
- const auto* board_info_am2 = GetValueAsString(ipmi_fru, "BOARD_INFO_AM2");
- const auto* product_info_am2 =
- GetValueAsString(ipmi_fru, "PRODUCT_INFO_AM2");
- if (board_product_name == nullptr && product_product_name == nullptr &&
- bus == nullptr && address == nullptr && part_number == nullptr &&
- board_info_am2 == nullptr && 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 board_info_am2_matches_or_null =
- CheckProbeFieldMatch(board_info_am2, fru, "BOARD_INFO_AM2");
- bool product_info_am2_matches_or_null =
- CheckProbeFieldMatch(product_info_am2, fru, "PRODUCT_INFO_AM2");
-
- // If the FRU doesn't match the probe, continue to the next FRU.
- if (!i2c_info_matches_or_null || !board_product_name_matches_or_null ||
- !product_product_name_matches_or_null ||
- !part_number_matches_or_null || !board_info_am2_matches_or_null ||
- !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) &&
+ // Post-process the configs with FOUND probe.
+ // Using topological sort (bfs) to find all the configs which FOUND probe
+ // rule is fulfilled. This is to avoid the recursion approach in
+ // https://github.com/openbmc/entity-manager/blob/7f51d32fb69f3bb6c4eabf685a27b57f345445cb/src/entity_manager/perform_scan.cpp#L582
+ std::queue<std::string> found_target_candidates;
+ for (const auto& [name, _] : probed_config_map) {
+ if (pending_config_name_to_probed_configs.find(name) !=
+ pending_config_name_to_probed_configs.end()) {
+ found_target_candidates.push(name);
+ }
+ }
+ while (!found_target_candidates.empty()) {
+ const std::string name = found_target_candidates.front();
+ found_target_candidates.pop();
+ auto found_config_it = pending_config_name_to_probed_configs.find(name);
+ if (found_config_it != pending_config_name_to_probed_configs.end()) {
+ for (const auto& found_config_data : found_config_it->second) {
+ const std::string config_name =
+ found_config_data.fru_keys[0].ToString();
+ if (probed_config_map.contains(config_name) &&
!absl::StrContains(name, "$index")) {
return absl::InvalidArgumentError(
- absl::StrCat("Invalid config: Frus match the same config multiple"
- " times but config name does not contain $index : ",
- name));
+ absl::StrCat("Invalid config: Multiple configs with FOUND probe"
+ " have the same name: ",
+ config_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;
+ found_target_candidates.push(config_name);
+ probed_config_map[config_name] = found_config_data;
+ Fru entity_object;
+ entity_object.mutable_attributes()->set_key(config_name);
+ entity_object.mutable_attributes()->set_status(STATUS_READY);
+ fru_table.mutable_key_to_fru()->insert({config_name, entity_object});
+ LOG(INFO) << "FOUND " << name << ", appending config " << config_name;
}
- // 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());
- }
+ pending_config_name_to_probed_configs.erase(found_config_it);
}
}
return probed_config_map;