| #include "tlbmc/redfish/routes/chassis.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/bind_front.h" |
| #include "absl/log/log.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "nlohmann/json.hpp" |
| #include "nlohmann/json_fwd.hpp" |
| #include "topology_config.pb.h" |
| #include "tlbmc/redfish/app.h" |
| #include "tlbmc/redfish/data/stable_id.h" |
| #include "stable_id.pb.h" |
| #include "tlbmc/redfish/request.h" |
| #include "tlbmc/redfish/response.h" |
| #include "tlbmc/redfish/url.h" |
| #include "fru.pb.h" |
| #include "resource.pb.h" |
| #include "tlbmc/store/store.h" |
| #include "google/protobuf/repeated_ptr_field.h" |
| |
| namespace milotic_tlbmc::chassis { |
| |
| namespace { |
| |
| void HandleChassisCollection(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp) { |
| resp.SetKeyInJsonBody("/@odata.id", "/redfish/v1/Chassis"); |
| resp.SetKeyInJsonBody("/@odata.type", "#ChassisCollection.ChassisCollection"); |
| resp.SetKeyInJsonBody("/Name", "Chassis Collection"); |
| |
| const Store& store = *app.GetStore(); |
| absl::StatusOr<std::vector<std::string>> config_names = |
| store.GetAllConfigNames(); |
| if (!config_names.ok()) { |
| resp.SetToAbslStatus(config_names.status()); |
| return; |
| } |
| |
| resp.SetKeyInJsonBody("/Members@odata.count", config_names->size()); |
| nlohmann::json::array_t members; |
| for (const auto& config : *config_names) { |
| nlohmann::json::object_t member; |
| member["@odata.id"] = CreateUrl({"redfish", "v1", "Chassis", config}); |
| members.push_back(std::move(member)); |
| } |
| std::sort(members.begin(), members.end()); |
| resp.SetKeyInJsonBody("/Members", members); |
| } |
| |
| void HandleChassis(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp, const std::string& chassis_id) { |
| const Store& store = *app.GetStore(); |
| nlohmann::json::json_pointer chassis_pointer(""); |
| // Chassis id corresponds to the config name. |
| absl::StatusOr<std::string> fru_key = store.GetFruKeyByConfigName(chassis_id); |
| if (!fru_key.ok()) { |
| resp.SetToAbslStatus(fru_key.status()); |
| return; |
| } |
| |
| absl::StatusOr<const Fru*> fru = store.GetFru(*fru_key); |
| if (!fru.ok()) { |
| resp.SetToAbslStatus(fru.status()); |
| return; |
| } |
| absl::StatusOr<const TopologyConfigNode*> topology_config_node = |
| store.GetFruTopology(chassis_id); |
| if (!topology_config_node.ok()) { |
| resp.SetToAbslStatus(topology_config_node.status()); |
| return; |
| } |
| FillResponseWithFruData(chassis_pointer, *fru, *topology_config_node, resp); |
| } |
| |
| inline std::string GetSystemId() { return "system"; } |
| |
| nlohmann::json::array_t CreateChildResourceLinksArray( |
| const ::google::protobuf::RepeatedPtrField<std::string>& child_resource_ids, |
| ResourceType resource_type) { |
| nlohmann::json::array_t child_resources; |
| for (const auto& id : child_resource_ids) { |
| nlohmann::json::object_t child_object; |
| switch (resource_type) { |
| case RESOURCE_TYPE_BOARD: |
| child_object["@odata.id"] = CreateUrl({"redfish", "v1", "Chassis", id}); |
| break; |
| case RESOURCE_TYPE_CABLE: |
| child_object["@odata.id"] = CreateUrl({"redfish", "v1", "Cables", id}); |
| break; |
| case RESOURCE_TYPE_PROCESSOR: |
| child_object["@odata.id"] = CreateUrl( |
| {"redfish", "v1", "Systems", GetSystemId(), "Processors", id}); |
| break; |
| case RESOURCE_TYPE_STORAGE: |
| child_object["@odata.id"] = CreateUrl( |
| {"redfish", "v1", "Systems", GetSystemId(), "Storage", id}); |
| break; |
| default: |
| break; |
| } |
| child_resources.emplace_back(child_object); |
| } |
| return child_resources; |
| } |
| } // namespace |
| |
| void FillResponseWithFruData( |
| const nlohmann::json::json_pointer& chassis_pointer, const Fru* fru_ptr, |
| const TopologyConfigNode* topology_config_node_ptr, RedfishResponse& resp) { |
| if (fru_ptr->attributes().status() != STATUS_READY) { |
| resp.SetToNotReady(); |
| return; |
| } |
| const std::string& chassis_id = topology_config_node_ptr->name(); |
| resp.SetKeyInJsonBody(chassis_pointer / "@odata.id", |
| "/redfish/v1/Chassis/" + chassis_id); |
| resp.SetKeyInJsonBody(chassis_pointer / "@odata.type", |
| "#Chassis.v1_17_0.Chassis"); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Assembly" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Assembly"})); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Certificates" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Certificates"})); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "TrustedComponents" / "@odata.id", |
| milotic_tlbmc::CreateUrl( |
| {"redfish", "v1", "Chassis", chassis_id, "TrustedComponents"})); |
| absl::string_view chassis_type; |
| switch (fru_ptr->attributes().chassis_properties().chassis_type()) { |
| case CHASSIS_TYPE_RACK_MOUNT: |
| chassis_type = "RackMount"; |
| break; |
| case CHASSIS_TYPE_MODULE: |
| chassis_type = "Module"; |
| break; |
| case CHASSIS_TYPE_STORAGE_ENCLOSURE: |
| chassis_type = "StorageEnclosure"; |
| break; |
| case CHASSIS_TYPE_COMPONENT: |
| chassis_type = "Component"; |
| break; |
| case CHASSIS_TYPE_STANDALONE: |
| chassis_type = "StandAlone"; |
| break; |
| default: |
| break; |
| } |
| resp.SetKeyInJsonBody(chassis_pointer / "ChassisType", chassis_type); |
| |
| if (fru_ptr->attributes().chassis_properties().bmcnet()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "NetworkAdapters" / "@odata.id", |
| milotic_tlbmc::CreateUrl( |
| {"redfish", "v1", "Chassis", chassis_id, "NetworkAdapters"})); |
| } |
| |
| // Support Chassis Reset Action only for Root Chassis. |
| if (topology_config_node_ptr->location_context().devpath() == "/phys" || |
| topology_config_node_ptr->location_context().devpath() == |
| absl::StrCat( |
| "/", topology_config_node_ptr->root_chassis_location_code())) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Actions" / "#Chassis.Reset" / "target", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Actions", |
| "Chassis.Reset"})); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Actions" / "#Chassis.Reset" / "@Redfish.ActionInfo", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "ResetActionInfo"})); |
| } |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Drives" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Drives"})); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Memory" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Systems", GetSystemId(), "Memory"})); |
| resp.SetKeyInJsonBody(chassis_pointer / "Id", chassis_id); |
| resp.SetKeyInJsonBody(chassis_pointer / "Name", chassis_id); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Sensors" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Sensors"})); |
| |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Thermal" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Thermal"})); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Power" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Power"})); |
| |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "ThermalSubsystem" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "ThermalSubsystem"})); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "PowerSubsystem" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "PowerSubsystem"})); |
| resp.SetKeyInJsonBody(chassis_pointer / "EnvironmentMetrics" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, |
| "EnvironmentMetrics"})); |
| |
| resp.SetKeyInJsonBody(chassis_pointer / "PowerState", "On"); |
| |
| resp.SetKeyInJsonBody(chassis_pointer / "PCIeDevices" / "@odata.id", |
| "/redfish/v1/Systems/system/PCIeDevices"); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "PCIeSlots" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "PCIeSlots"})); |
| |
| if (fru_ptr->data().has_asset_info()) { |
| const AssetInfo& asset_info = fru_ptr->data().asset_info(); |
| if (!asset_info.manufacturer().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Manufacturer", |
| asset_info.manufacturer()); |
| } |
| if (!asset_info.product_name().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Model", |
| asset_info.product_name()); |
| } |
| if (!asset_info.serial_number().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "SerialNumber", |
| asset_info.serial_number()); |
| } |
| if (!asset_info.part_number().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "PartNumber", |
| asset_info.part_number()); |
| } |
| } |
| |
| if (topology_config_node_ptr->has_location_context()) { |
| StableId stable_id = GetStableId(*topology_config_node_ptr); |
| |
| if (!stable_id.service_label().empty()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Location" / "PartLocation" / "ServiceLabel", |
| stable_id.service_label()); |
| if (stable_id.has_location_type()) { |
| absl::string_view location_type; |
| switch (stable_id.location_type()) { |
| case PART_LOCATION_TYPE_BACKPLANE: |
| location_type = "Backplane"; |
| break; |
| case PART_LOCATION_TYPE_BAY: |
| location_type = "Bay"; |
| break; |
| case PART_LOCATION_TYPE_EMBEDDED: |
| location_type = "Embedded"; |
| break; |
| case PART_LOCATION_TYPE_SLOT: |
| location_type = "Slot"; |
| break; |
| case PART_LOCATION_TYPE_SOCKET: |
| location_type = "Socket"; |
| break; |
| default: |
| break; |
| } |
| if (!location_type.empty()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Location" / "PartLocation" / "LocationType", |
| location_type); |
| } |
| } |
| } |
| if (!stable_id.part_location_context().empty() && |
| stable_id.part_location_context() != "phys") { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Location" / "PartLocationContext", |
| stable_id.part_location_context()); |
| } |
| if (stable_id.has_embedded_location_context()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Location" / "Oem" / "Google" / |
| "EmbeddedLocationContext", |
| stable_id.embedded_location_context()); |
| } |
| // Populate special devpath field for embedded chassis. |
| if (stable_id.location_type() == |
| PART_LOCATION_TYPE_EMBEDDED) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Location" / "Oem" / "Google" / |
| "Devpath", |
| stable_id.machine_local_devpath()); |
| } |
| } |
| |
| nlohmann::json::array_t child_chassis = CreateChildResourceLinksArray( |
| topology_config_node_ptr->children_chassis_ids(), RESOURCE_TYPE_BOARD); |
| if (!child_chassis.empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Contains", |
| child_chassis); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Contains@odata.count", |
| child_chassis.size()); |
| } |
| |
| nlohmann::json::array_t child_cables = CreateChildResourceLinksArray( |
| topology_config_node_ptr->children_cable_ids(), RESOURCE_TYPE_CABLE); |
| if (!child_cables.empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables", child_cables); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables@odata.count", |
| child_cables.size()); |
| } |
| |
| nlohmann::json::array_t child_processors = CreateChildResourceLinksArray( |
| topology_config_node_ptr->children_processor_ids(), |
| RESOURCE_TYPE_PROCESSOR); |
| if (!child_processors.empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Processors", |
| child_processors); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Processors@odata.count", |
| child_processors.size()); |
| } |
| |
| nlohmann::json::array_t child_storages = CreateChildResourceLinksArray( |
| topology_config_node_ptr->children_storage_ids(), RESOURCE_TYPE_STORAGE); |
| if (!child_storages.empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Storage", |
| child_storages); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Storage@odata.count", |
| child_storages.size()); |
| } |
| |
| if (topology_config_node_ptr->has_parent_resource_id()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Links" / "ContainedBy" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", |
| topology_config_node_ptr->parent_resource_id()})); |
| } |
| |
| nlohmann::json::array_t managed_by; |
| nlohmann::json::object_t manager; |
| manager["@odata.id"] = "/redfish/v1/Managers/bmc"; |
| managed_by.emplace_back(manager); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "ManagedBy", managed_by); |
| |
| // TODO(davtang): Add support for multihost systems |
| nlohmann::json::array_t systems; |
| nlohmann::json::object_t system; |
| system["@odata.id"] = "/redfish/v1/Systems/system"; |
| systems.emplace_back(system); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "ComputerSystems", systems); |
| |
| resp.SetKeyInJsonBody(chassis_pointer / "Status" / "Health", "OK"); |
| resp.SetKeyInJsonBody(chassis_pointer / "Status" / "HealthRollup", "OK"); |
| resp.SetKeyInJsonBody(chassis_pointer / "Status" / "State", "Enabled"); |
| } |
| |
| void RegisterRoutes(RedfishApp& app) { |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleChassisCollection, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleChassis, std::cref(app))); |
| } |
| |
| } // namespace milotic_tlbmc::chassis |