| #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/str_format.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) { | 
 |   const std::string& chassis_id = topology_config_node_ptr->name(); | 
 |  | 
 |   if (fru_ptr->attributes().status() != STATUS_READY) { | 
 |     resp.SetToNotReady( | 
 |         absl::StrFormat("Chassis %s is not ready in tlBMC Store", chassis_id)); | 
 |     return; | 
 |   } | 
 |   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 |