Create `GetAllFruInfo` RPC
This RPC now returns all devpaths under ["/redfish/v1/Chassis", "/redfish/v1/Systems/system/Storage", "/redfish/v1/Systems/system/Memory", "/redfish/v1/Systems/system/Processor"].
TODO:
1. Make the root paths config-based.
2. Apply request filters to select matched devpaths instead of all devpaths.
3. [Long term?] Add more info into the response, instead of just returning the devpath list.
4. Integrate the RPC into BMCWeb.
5. Create a local server & client?
Tested:
Unit test
PiperOrigin-RevId: 769374568
Change-Id: I21289e74a81e84fabcd8e9ed782807e5944768e5
diff --git a/tlbmc/fru_component_model.proto b/tlbmc/fru_component_model.proto
new file mode 100644
index 0000000..e402c7a
--- /dev/null
+++ b/tlbmc/fru_component_model.proto
@@ -0,0 +1,156 @@
+edition = "2023";
+
+package milotic_fast_sanity;
+
+// Enum defining the standard types of unique identifiers for components.
+enum UniqueIdentifierType {
+ UNIQUE_IDENTIFIER_TYPE_UNSPECIFIED = 0;
+ UNIQUE_IDENTIFIER_TYPE_LOCAL_DEVPATH = 1;
+ UNIQUE_IDENTIFIER_TYPE_MACHINE_DEVPATH = 2;
+ UNIQUE_IDENTIFIER_TYPE_LOGICAL_NAME = 3;
+ UNIQUE_IDENTIFIER_TYPE_UUID = 4;
+ UNIQUE_IDENTIFIER_TYPE_ADDRESS = 5;
+ // Add other common identifier types as needed.
+}
+
+// Represents a unique identifier for a component, specifying its value and
+// type.
+message UniqueIdentifier {
+ string value = 1;
+ repeated string short_names = 2;
+ UniqueIdentifierType type = 3;
+}
+
+// Generic SMBus Location information.
+message I2cLocation {
+ string bus = 1;
+ string address = 2;
+}
+
+// Generic Location details for a component, allowing for different types of
+// physical locations.
+message Location {
+ oneof location_type {
+ I2cLocation i2c_location = 1;
+ // Extend with other location types as needed.
+ }
+}
+
+// Defines the type of connector.
+// This is a subset of the connector types defined in the redfish spec.
+enum ConnectorType {
+ CONNECTOR_TYPE_UNSPECIFIED = 0;
+ CONNECTOR_TYPE_AC_POWER = 1;
+ CONNECTOR_TYPE_DB9 = 2;
+ CONNECTOR_TYPE_DC_POWER = 3;
+ CONNECTOR_TYPE_DISPLAY_PORT = 4;
+ CONNECTOR_TYPE_HDMI = 5;
+ CONNECTOR_TYPE_ICI = 6;
+ CONNECTOR_TYPE_IPASS = 7;
+ CONNECTOR_TYPE_PCI_E = 8;
+ CONNECTOR_TYPE_PROPRIETARY = 9;
+ CONNECTOR_TYPE_RJ45 = 10;
+ CONNECTOR_TYPE_SATA = 11;
+ CONNECTOR_TYPE_SCSI = 12;
+ CONNECTOR_TYPE_SLIMSAS = 13;
+ CONNECTOR_TYPE_SFP = 14;
+ CONNECTOR_TYPE_SFP_PLUS = 15;
+ CONNECTOR_TYPE_USBA = 16;
+ CONNECTOR_TYPE_USBC = 17;
+ CONNECTOR_TYPE_QSFP = 18;
+ CONNECTOR_TYPE_CDFP = 19;
+ CONNECTOR_TYPE_OSFP = 20;
+ // Add more as needed
+}
+
+// Defines the type of cable per the redfish spec.
+enum CableType {
+ CABLE_TYPE_UNSPECIFIED = 0;
+ CABLE_TYPE_POWER = 1;
+ CABLE_TYPE_NETWORK = 2;
+ CABLE_TYPE_STORAGE = 3;
+ CABLE_TYPE_FAN = 4;
+ CABLE_TYPE_PCIE = 5;
+ CABLE_TYPE_USB = 6;
+ CABLE_TYPE_VIDEO = 7;
+ CABLE_TYPE_FABRIC = 8;
+ CABLE_TYPE_SERIAL = 9;
+ CABLE_TYPE_GENERAL = 10;
+
+ // Add more as needed
+}
+
+// FRI Classification
+enum ComponentClassification {
+ COMPONENT_CLASSIFICATION_UNSPECIFIED = 0;
+ COMPONENT_CLASSIFICATION_MEMORY = 1;
+ COMPONENT_CLASSIFICATION_PROCCESSOR = 2;
+ COMPONENT_CLASSIFICATION_CABLE = 3;
+ COMPONENT_CLASSIFICATION_FAN = 4;
+ COMPONENT_CLASSIFICATION_DRIVE = 5;
+ COMPONENT_CLASSIFICATION_BOARD = 6;
+}
+
+// Defines the general category or type of this component.
+enum ComponentType {
+ COMPONENT_NODE_TYPE_UNSPECIFIED = 0;
+ COMPONENT_NODE_TYPE_PLUGIN = 1; // e.g., Motherboard, Tray, DIMM, etc.
+ COMPONENT_NODE_TYPE_DEVICE = 2; // e.g., Embedded ROT, controller, etc.
+}
+
+// Describes various relationships this component has with others.
+message RelatedComponents {
+ // Components (devices, connectors) that are direct children or contained
+ // within this component.
+ repeated UniqueIdentifier child_components = 1;
+
+ // The parent component(s) this component is part of or plugged into.
+ UniqueIdentifier parent_component = 2;
+}
+
+// Represents a single Field Replaceable Unit (FRU) or a logical component.
+message FruComponent {
+ UniqueIdentifier primary_identifier = 1;
+ repeated UniqueIdentifier secondary_identifiers = 2;
+
+ // The type of FRU.
+ ComponentType fru_type = 3;
+
+ // The class of FRU.
+ ComponentClassification fru_class = 4;
+
+ // Location details of the component.
+ Location location = 5;
+
+ // Information specific to connector components.
+ ConnectorType connector_type = 6;
+
+ // Information specific to cable components.
+ CableType cable_type = 7;
+
+ // Structured relationships describing the component's children, parents, and
+ // plugged-in items.
+ RelatedComponents relationships = 8;
+
+ // A FRU can be in the following states:
+ // 1. OK: The FRU is present.
+ //
+ // The following states are only applicable if the intent model is available
+ // on the server either provided as part of the request or natively present on
+ // the server.
+ // 2. NOT_SCANNED: The FRU has not been scanned.
+ // The implemenatations that don't have an
+ // intent model will not be able to report this status per FRU. In such
+ // cases, a FRU may be reported without an identifier
+ // 3. NOT_FOUND: The FRU has not been found.
+ // The implementations that scan all the FRUs on the system may choose
+ // to report this status for FRUs that are not present by comparing the
+ // scanned FRUs with the intent model.
+ enum Status {
+ STATUS_UNKNOWN = 0;
+ STATUS_OK = 1;
+ STATUS_NOT_SCANNED = 2;
+ STATUS_NOT_FOUND = 3;
+ }
+ Status status = 9;
+}
diff --git a/tlbmc/fru_service.proto b/tlbmc/fru_service.proto
new file mode 100644
index 0000000..f4397ff
--- /dev/null
+++ b/tlbmc/fru_service.proto
@@ -0,0 +1,46 @@
+edition = "2023";
+
+package milotic_fast_sanity;
+
+import "fru_component_model.proto";
+
+message GetAllFruInfoRequest {
+ // Optional filter by the general component node type.
+ // Only FRUs matching these types will be returned.
+ // This is an OR filter.
+ repeated ComponentType filter_by_fru_type = 1;
+
+ // Optional filter by the FRU class.
+ // Only FRUs matching this FruClass will be returned.
+ repeated ComponentClassification filter_by_fru_class = 2;
+
+ // Optional filter by an identifier's value.
+ // This is an OR filter.
+ repeated UniqueIdentifier filter_by_identifier_value = 3;
+
+ // List of expected FRU components.
+ repeated UniqueIdentifier expected_fru_components = 4;
+
+ enum FruScanType {
+ FRU_SCAN_TYPE_UNSPECIFIED = 0;
+ // Performs a scan of the expected FRUs only provided in the request.
+ FRU_SCAN_TYPE_EXPECTED_ONLY = 1;
+ // Performs a scan of all FRUs on the system.
+ FRU_SCAN_TYPE_ALL = 2;
+ }
+
+ // Optional filter by the type of FRU scan to perform.
+ // If unspecified, defaults to FRU_SCAN_TYPE_ALL.
+ FruScanType fru_scan_type = 5 [default = FRU_SCAN_TYPE_ALL];
+}
+
+message GetAllFruInfoResponse {
+ repeated FruComponent fru_components = 1;
+}
+
+service FruService {
+ // Get the FRU information for a given component.
+ rpc GetAllFruInfo(GetAllFruInfoRequest) returns (GetAllFruInfoResponse) {
+ option deadline = 60.0;
+ }
+}
diff --git a/tlbmc/service/fru_service.cc b/tlbmc/service/fru_service.cc
new file mode 100644
index 0000000..bbb4446
--- /dev/null
+++ b/tlbmc/service/fru_service.cc
@@ -0,0 +1,362 @@
+#include "tlbmc/service/fru_service.h"
+
+#include <array>
+#include <memory>
+#include <string>
+#include <system_error> // NOLINT
+#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/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
+#include "grpcpp/support/status.h"
+#include "nlohmann/json_fwd.hpp"
+#include "json_utils.h"
+#include "fru_component_model.proto.h"
+#include "fru_service.proto.h"
+#include "dbus_utility.hpp" // NOLINT
+#include "async_resp.hpp" // NOLINT
+#include "http_request.hpp" // NOLINT
+#include "util/task/status_macros.h"
+
+namespace milotic_fast_sanity {
+
+namespace {
+
+using ::milotic::authz::GetValueAsJson;
+using ::milotic::authz::GetValueAsString;
+
+constexpr absl::string_view kRootChassisUrl = "/redfish/v1/Chassis?$expand=.";
+// TODO(b/415893570): Make this config-based.
+// Be ware: BB has `"/redfish/v1/Systems/system1/Memory"` as the memory
+// Redfish path.
+constexpr std::array<absl::string_view, 5> kRootUrls = {
+ "/redfish/v1/Chassis?$expand=.($levels=2)",
+ "/redfish/v1/Cables?$expand=.($levels=1)",
+ "/redfish/v1/Systems/system/Storage?$expand=.($levels=1)",
+ "/redfish/v1/Systems/system/Memory?$expand=.($levels=1)",
+ "/redfish/v1/Systems/system/Processors?$expand=.($levels=2)"};
+// local_devpath3 = "/" + LocalRootChassisLocationCode + PartLocationContext +
+// ServiceLabel
+constexpr absl::string_view kLocalRootChassisLocationCode = "phys";
+
+// TODO(b/422818770): Add a test case for this util function.
+// Create Internal Request to query given `url`.
+absl::StatusOr<crow::Request> CreateRedfishRequest(
+ absl::string_view url, boost::beast::http::verb method) {
+ boost::beast::http::request<boost::beast::http::string_body> boost_request;
+ boost_request.target(url);
+ // TODO(b/422818770): Add the server & client pair.
+ boost_request.method(method);
+ std::error_code error;
+ crow::Request crow_request(boost_request, error);
+ if (error) {
+ return absl::InternalError("Error creating crow_request.");
+ }
+
+ crow_request.fromGrpc = false;
+ return crow_request;
+}
+
+absl::Status IsLocationInfoValid(const nlohmann::json &fru) {
+ // TODO(b/422818770): Add a test case to cover the missing
+ // location info case.
+ const nlohmann::json *fru_location = GetValueAsJson(fru, "Location");
+ if (fru_location == nullptr) {
+ return absl::NotFoundError("Location info not found.");
+ }
+
+ const nlohmann::json *fru_location_part_location =
+ GetValueAsJson(*fru_location, "PartLocation");
+ if (fru_location_part_location == nullptr) {
+ return absl::NotFoundError("Location.PartLocation info not found.");
+ }
+
+ const nlohmann::json *fru_location_part_location_service_label =
+ GetValueAsJson(*fru_location_part_location, "ServiceLabel");
+ if (fru_location_part_location_service_label == nullptr) {
+ return absl::NotFoundError(
+ "Location.PartLocation.ServiceLabel info not found.");
+ }
+
+ return absl::OkStatus();
+}
+
+absl::StatusOr<std::string> ExtractRootChassisLocationCodeFromJson(
+ const nlohmann::json &json) {
+ if (!json.contains("Members")) {
+ return absl::InternalError(
+ "Redfish response does not contain `Members` field.");
+ }
+
+ for (const auto &fru : json["Members"]) {
+ // The root node's indegree must be 0.
+ if (fru.contains("Links") && fru["Links"].contains("ContainedBy")) {
+ continue;
+ }
+
+ // Make sure there is valid location info.
+ if (!IsLocationInfoValid(fru).ok()) {
+ continue;
+ }
+
+ return *GetValueAsString(fru["Location"]["PartLocation"], "ServiceLabel");
+ }
+
+ return absl::NotFoundError("Root chassis location code not found.");
+}
+
+absl::StatusOr<Devpath> ExtractDevpathFromJson(
+ const nlohmann::json &json, absl::string_view root_chassis_location_code) {
+ // TODO(b/422818770): Add a test case to cover the missing
+ // location info case.
+ RETURN_IF_ERROR(IsLocationInfoValid(json));
+
+ // Ensure this is a FRU.
+ const auto location_type_it =
+ json["Location"]["PartLocation"].find("LocationType");
+ if (location_type_it == json["Location"]["PartLocation"].end() ||
+ (*location_type_it) != "Slot") {
+ return Devpath();
+ }
+
+ // This is the root chassis. Return empty devpath.
+ const auto &res_member_location = json["Location"];
+ std::string service_label =
+ res_member_location["PartLocation"]["ServiceLabel"];
+ if (service_label == root_chassis_location_code) {
+ return Devpath();
+ }
+
+ // PartLocationContext is optional, and the prefix should be removed if it is
+ // equal to the root chassis location code.
+ std::string part_location_context;
+ const auto part_location_context_it =
+ res_member_location.find("PartLocationContext");
+ if (part_location_context_it != res_member_location.end()) {
+ part_location_context = res_member_location.at("PartLocationContext");
+ }
+ bool start_with_root_chassis_location_code =
+ absl::StartsWith(part_location_context, root_chassis_location_code);
+ bool start_with_local_root_chassis_location_code =
+ absl::StartsWith(part_location_context, kLocalRootChassisLocationCode);
+ if (start_with_root_chassis_location_code ||
+ start_with_local_root_chassis_location_code) {
+ // Note that RootChassisLocationCode should be eliminated if it is the
+ // prefix of either `PartLocationContext` or `ServiceLabel`.
+ part_location_context =
+ start_with_root_chassis_location_code
+ ? part_location_context.substr(root_chassis_location_code.length())
+ : part_location_context.substr(
+ kLocalRootChassisLocationCode.length());
+ }
+
+ return Devpath(part_location_context, service_label);
+}
+
+void InsertDevpath(const nlohmann::json &json,
+ absl::string_view root_chassis_location_code,
+ std::shared_ptr<PartLocationContextToServiceLabelSet>
+ &part_location_context_to_service_label_set) {
+ absl::StatusOr<Devpath> devpath_status =
+ ExtractDevpathFromJson(json, root_chassis_location_code);
+ // TODO(b/422818770): Utilize the error status better.
+ // E.g., provide logs.
+ if (!devpath_status.ok() || devpath_status.value().service_label.empty()) {
+ return;
+ }
+
+ absl::MutexLock lock(&part_location_context_to_service_label_set->mutex);
+ part_location_context_to_service_label_set
+ ->part_location_context_to_service_label_set[devpath_status.value()
+ .part_location_context]
+ .insert(devpath_status.value().service_label);
+}
+
+void GenerateDevpathFromRedfishObject(
+ const nlohmann::json &json, absl::string_view root_chassis_location_code,
+ std::shared_ptr<PartLocationContextToServiceLabelSet>
+ part_location_context_to_service_label_set) {
+ InsertDevpath(json, root_chassis_location_code,
+ part_location_context_to_service_label_set);
+ const auto assembly_it = json.find("Assembly");
+ if (assembly_it != json.end() &&
+ assembly_it->find("Assemblies") != json["Assembly"].end()) {
+ for (const auto &fru : (*assembly_it)["Assemblies"]) {
+ GenerateDevpathFromRedfishObject(
+ fru, root_chassis_location_code,
+ part_location_context_to_service_label_set);
+ }
+ }
+ if (json.contains("Members")) {
+ for (const auto &fru : json["Members"]) {
+ GenerateDevpathFromRedfishObject(
+ fru, root_chassis_location_code,
+ part_location_context_to_service_label_set);
+ }
+ }
+}
+
+void SendGetAllFruInfoResponse(
+ ServerUnaryReactor &reactor, GetAllFruInfoResponse &response,
+ const std::shared_ptr<PartLocationContextToServiceLabelSet>
+ &part_location_context_to_service_label_set) {
+ // Build the devpath list
+ // Insert the default local root chassis devpath first.
+ std::vector<std::string> devpaths({"/phys"});
+
+ {
+ absl::MutexLock lock(&part_location_context_to_service_label_set->mutex);
+ for (const auto &[part_location_context, service_label_set] :
+ part_location_context_to_service_label_set
+ ->part_location_context_to_service_label_set) {
+ for (const auto &service_label : service_label_set) {
+ devpaths.push_back(absl::StrCat(
+ "/", kLocalRootChassisLocationCode, "/",
+ (!part_location_context.empty() ? part_location_context + "/" : ""),
+ service_label));
+ }
+ }
+ }
+
+ if (devpaths.empty()) {
+ reactor.Finish(absl::NotFoundError("No matched devpath found."));
+ }
+
+ for (const std::string &devpath : devpaths) {
+ if (devpath.empty()) {
+ continue;
+ }
+ FruComponent fru;
+ fru.mutable_primary_identifier()->set_value(devpath);
+ fru.mutable_primary_identifier()->set_type(
+ UniqueIdentifierType::UNIQUE_IDENTIFIER_TYPE_LOCAL_DEVPATH);
+ response.mutable_fru_components()->Add(std::move(fru));
+ }
+ reactor.Finish(absl::OkStatus());
+}
+
+void DecreaseOngoingDevpathExtractionRequestCount(
+ const std::shared_ptr<OngoingDevpathExtractionRequestCount>
+ &ongoing_devpath_extraction_request_count) {
+ absl::MutexLock lock(&ongoing_devpath_extraction_request_count->mutex);
+ --ongoing_devpath_extraction_request_count
+ ->ongoing_devpath_extraction_request_count;
+}
+
+} // namespace
+
+FruServiceImpl::FruServiceImpl(App *app, const FruServiceOptions &options)
+ : app_(app) {}
+
+ServerUnaryReactor *FruServiceImpl::GetAllFruInfo(
+ CallbackServerContext *context, const GetAllFruInfoRequest *request,
+ GetAllFruInfoResponse *response) {
+ ServerUnaryReactor *reactor = context->DefaultReactor();
+ GenerateMatchedDevpathsFromRedfishPaths(*reactor, *request, *response);
+ return reactor;
+}
+
+void FruServiceImpl::GenerateMatchedDevpathsFromRedfishPaths(
+ ServerUnaryReactor &reactor, const GetAllFruInfoRequest &request,
+ GetAllFruInfoResponse &response) {
+ // Extract the root chassis location code from the first url.
+ absl::StatusOr<crow::Request> crow_request_status =
+ CreateRedfishRequest(kRootChassisUrl, boost::beast::http::verb::get);
+ if (!crow_request_status.ok()) {
+ LOG(WARNING) << "Error creating redfish request for url: `"
+ << kRootChassisUrl << "`; with status: `"
+ << crow_request_status.status() << "`";
+ reactor.Finish(absl::InternalError(absl::StrCat(
+ "Error creating redfish request for root chassis location code url: `",
+ kRootChassisUrl, "`.")));
+ return;
+ }
+
+ auto async_resp = std::make_shared<bmcweb::AsyncResp>(nullptr);
+ async_resp->response_type =
+ bmcweb::AsyncResp::ResponseType::kOriginOfCondition;
+ async_resp->res.setCompleteRequestHandler(
+ [this, &reactor, &request, &response](crow::Response &res) {
+ if (!res.stringResponse.has_value()) {
+ reactor.Finish(absl::InternalError(absl::StrCat(
+ "No valid response from redfish for root chassis url: `",
+ kRootChassisUrl, "`.")));
+ return;
+ }
+ absl::StatusOr<std::string> root_chassis_location_code_status =
+ ExtractRootChassisLocationCodeFromJson(res.jsonValue);
+ if (!root_chassis_location_code_status.ok()) {
+ reactor.Finish(root_chassis_location_code_status.status());
+ return;
+ }
+
+ RequestRedfishAndExtractDevpaths(reactor, request, response,
+ *root_chassis_location_code_status);
+ });
+ app_->handle(*crow_request_status, async_resp);
+}
+
+void FruServiceImpl::RequestRedfishAndExtractDevpaths(
+ ServerUnaryReactor &reactor, const GetAllFruInfoRequest &request,
+ GetAllFruInfoResponse &response,
+ absl::string_view root_chassis_location_code) {
+ std::shared_ptr<PartLocationContextToServiceLabelSet>
+ part_location_context_to_service_label_set =
+ std::make_shared<PartLocationContextToServiceLabelSet>();
+
+ std::shared_ptr<OngoingDevpathExtractionRequestCount>
+ ongoing_devpath_extraction_request_count =
+ std::make_shared<OngoingDevpathExtractionRequestCount>(
+ kRootUrls.size());
+
+ for (absl::string_view url : kRootUrls) {
+ absl::StatusOr<crow::Request> devpath_request_status =
+ CreateRedfishRequest(url, boost::beast::http::verb::get);
+ if (!devpath_request_status.ok()) {
+ LOG(WARNING) << "Error creating redfish request for url: `" << url
+ << "`; with status: `" << devpath_request_status.status()
+ << "`";
+ continue;
+ }
+ auto async_devpath_resp = std::make_shared<bmcweb::AsyncResp>(nullptr);
+ async_devpath_resp->response_type =
+ bmcweb::AsyncResp::ResponseType::kOriginOfCondition;
+
+ // Extract devpaths from the rest of the requests.
+ async_devpath_resp->res.setCompleteRequestHandler(
+ [&reactor, &response, url, part_location_context_to_service_label_set,
+ root_chassis_location_code,
+ ongoing_devpath_extraction_request_count](crow::Response &res) {
+ if (res.stringResponse.has_value()) {
+ GenerateDevpathFromRedfishObject(
+ res.jsonValue, root_chassis_location_code,
+ part_location_context_to_service_label_set);
+ } else {
+ LOG(WARNING) << "No valid response from redfish for url: `" << url
+ << "`";
+ }
+
+ DecreaseOngoingDevpathExtractionRequestCount(
+ ongoing_devpath_extraction_request_count);
+
+ absl::MutexLock lock(
+ &ongoing_devpath_extraction_request_count->mutex);
+ if (ongoing_devpath_extraction_request_count
+ ->ongoing_devpath_extraction_request_count == 0) {
+ SendGetAllFruInfoResponse(
+ reactor, response, part_location_context_to_service_label_set);
+ }
+ });
+ app_->handle(*devpath_request_status, async_devpath_resp);
+ }
+}
+
+} // namespace milotic_fast_sanity
diff --git a/tlbmc/service/fru_service.h b/tlbmc/service/fru_service.h
new file mode 100644
index 0000000..25e868a
--- /dev/null
+++ b/tlbmc/service/fru_service.h
@@ -0,0 +1,80 @@
+#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_FAST_SANITY_SERVICE_FRU_SERVICE_H_
+#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_FAST_SANITY_SERVICE_FRU_SERVICE_H_
+
+#include <cstdint>
+#include <string>
+
+#include "absl/base/thread_annotations.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
+#include "grpcpp/server_context.h"
+#include "grpcpp/support/server_callback.h"
+#include "fru_service.grpc.pb.h"
+#include "dbus_utility.hpp" // NOLINT
+#include "async_resp.hpp" // NOLINT
+#include "http_request.hpp" // NOLINT
+#include "zatar/bmcweb_cert_provider.h"
+
+namespace milotic_fast_sanity {
+
+using ::grpc::CallbackServerContext;
+using ::grpc::ServerUnaryReactor;
+
+struct Devpath {
+ std::string part_location_context;
+ std::string service_label;
+
+ Devpath() = default;
+
+ Devpath(absl::string_view part_location_context,
+ absl::string_view service_label)
+ : part_location_context(part_location_context),
+ service_label(service_label) {}
+};
+
+struct PartLocationContextToServiceLabelSet {
+ absl::Mutex mutex;
+ absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>
+ part_location_context_to_service_label_set ABSL_GUARDED_BY(mutex);
+};
+
+struct FruServiceOptions {
+ bool enable_authorization = true;
+ ::milotic::redfish::BmcWebCertProvider *cert_provider = nullptr;
+};
+
+struct OngoingDevpathExtractionRequestCount {
+ absl::Mutex mutex;
+ uint32_t ongoing_devpath_extraction_request_count ABSL_GUARDED_BY(mutex);
+
+ explicit OngoingDevpathExtractionRequestCount(
+ uint32_t ongoing_devpath_extraction_request_count)
+ : ongoing_devpath_extraction_request_count(
+ ongoing_devpath_extraction_request_count) {}
+};
+
+class FruServiceImpl final : public grpc::FruService::CallbackService {
+ public:
+ explicit FruServiceImpl(App *app, const FruServiceOptions &options);
+
+ ServerUnaryReactor *GetAllFruInfo(CallbackServerContext *context,
+ const GetAllFruInfoRequest *request,
+ GetAllFruInfoResponse *response) override;
+
+ protected:
+ void GenerateMatchedDevpathsFromRedfishPaths(
+ ServerUnaryReactor &reactor, const GetAllFruInfoRequest &request,
+ GetAllFruInfoResponse &response);
+ void RequestRedfishAndExtractDevpaths(
+ ServerUnaryReactor &reactor, const GetAllFruInfoRequest &request,
+ GetAllFruInfoResponse &response,
+ absl::string_view root_chassis_location_code);
+
+ App *app_;
+};
+
+} // namespace milotic_fast_sanity
+
+#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_FAST_SANITY_SERVICE_FRU_SERVICE_H_