| #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/status.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 "json_utils.h" |
| #include "tlbmc/central_config/config.h" |
| #include "tlbmc/collector/led_collector.h" |
| #include "tlbmc/collector/power_control_collector.h" |
| #include "led_config.pb.h" |
| #include "power_control.pb.h" |
| #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 "tlbmc/resource/reset_type.h" |
| #include "resource.pb.h" |
| #include "tlbmc/store/store.h" |
| #include "google/protobuf/repeated_ptr_field.h" |
| |
| namespace milotic_tlbmc::chassis { |
| |
| using ::milotic::authz::GetValueAsBool; |
| using ::milotic::authz::GetValueAsString; |
| |
| 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_keys = |
| store.GetAllConfigKeys(); |
| if (!config_keys.ok()) { |
| resp.SetToAbslStatus(config_keys.status()); |
| return; |
| } |
| |
| std::vector<std::string> chassis_ids; |
| for (const auto& key : *config_keys) { |
| absl::StatusOr<std::string> fru_key = store.GetFruKeyByConfigKey(key); |
| if (!fru_key.ok()) { |
| continue; |
| } |
| absl::StatusOr<const Fru*> fru = store.GetFru(*fru_key); |
| if (!fru.ok()) { |
| LOG(WARNING) << "Failed to get FRU with key " << *fru_key |
| << " from store: " << fru.status(); |
| continue; |
| } |
| |
| if ((*fru)->attributes().resource_type() != RESOURCE_TYPE_BOARD) { |
| continue; |
| } |
| |
| chassis_ids.push_back(key); |
| } |
| std::sort(chassis_ids.begin(), chassis_ids.end()); |
| |
| nlohmann::json::array_t members = nlohmann::json::array_t(); |
| for (const auto& id : chassis_ids) { |
| nlohmann::json::object_t member; |
| member["@odata.id"] = CreateUrl({"redfish", "v1", "Chassis", id}); |
| members.push_back(std::move(member)); |
| } |
| resp.SetKeyInJsonBody("/Members", members); |
| resp.SetKeyInJsonBody("/Members@odata.count", chassis_ids.size()); |
| } |
| |
| 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 key. |
| absl::StatusOr<std::string> fru_key = store.GetFruKeyByConfigKey(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, store, |
| resp); |
| } |
| |
| inline std::string GetSystemId() { |
| if (GetTlbmcConfig().fru_collector_module().expose_default_system_link()) { |
| return "system"; |
| } |
| return ""; |
| } |
| |
| void PopulateManagedByLink(const nlohmann::json::json_pointer& chassis_pointer, |
| const TopologyConfigNode& topology_config_node, |
| RedfishResponse& resp) { |
| nlohmann::json::array_t managed_by; |
| // If no managed by ids are provided, use the default manager id "bmc". |
| if (topology_config_node.managed_by_ids().empty()) { |
| nlohmann::json::object_t manager; |
| manager["@odata.id"] = "/redfish/v1/Managers/bmc"; |
| managed_by.push_back(std::move(manager)); |
| } else { |
| for (const std::string& manager_id : |
| topology_config_node.managed_by_ids()) { |
| nlohmann::json::object_t manager; |
| manager["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Managers", manager_id}); |
| managed_by.push_back(std::move(manager)); |
| } |
| } |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "ManagedBy", managed_by); |
| } |
| |
| nlohmann::json::array_t CreateChildResourceLinksArray( |
| const ::google::protobuf::RepeatedPtrField<std::string>& child_resource_ids, |
| ResourceType resource_type, |
| const ::google::protobuf::RepeatedPtrField<std::string>& system_ids) { |
| nlohmann::json::array_t child_resources; |
| for (const auto& id : child_resource_ids) { |
| nlohmann::json::object_t child_object; |
| if (resource_type == RESOURCE_TYPE_BOARD) { |
| child_object["@odata.id"] = CreateUrl({"redfish", "v1", "Chassis", id}); |
| child_resources.push_back(std::move(child_object)); |
| } else if (resource_type == RESOURCE_TYPE_CABLE) { |
| child_object["@odata.id"] = CreateUrl({"redfish", "v1", "Cables", id}); |
| child_resources.push_back(std::move(child_object)); |
| } else if (resource_type == RESOURCE_TYPE_PROCESSOR) { |
| if (system_ids.empty()) { |
| // If no system ids are provided, use the default system id "system". |
| if (GetTlbmcConfig() |
| .fru_collector_module() |
| .expose_default_system_link()) { |
| child_object["@odata.id"] = CreateUrl( |
| {"redfish", "v1", "Systems", GetSystemId(), "Processors", id}); |
| child_resources.push_back(std::move(child_object)); |
| } |
| } else { |
| for (const auto& system_id : system_ids) { |
| nlohmann::json::object_t child_object; |
| child_object["@odata.id"] = CreateUrl( |
| {"redfish", "v1", "Systems", system_id, "Processors", id}); |
| child_resources.push_back(std::move(child_object)); |
| } |
| } |
| } |
| } |
| return child_resources; |
| } |
| |
| // Injects the IndicatorLED and LocationIndicatorActive properties into the |
| // chassis Redfish response if an indicator LED is configured. |
| void InjectIndicatorLed(const Store& store, |
| const nlohmann::json::json_pointer& chassis_pointer, |
| RedfishResponse& resp) { |
| LedCollector* led_collector = store.GetLedCollector(); |
| if (led_collector == nullptr) { |
| return; |
| } |
| if (!led_collector->HasIndicatorConfig()) { |
| return; |
| } |
| if (absl::StatusOr<LedState> state = led_collector->GetIndicatorState(); |
| state.ok()) { |
| switch (*state) { |
| case LED_STATE_ON: |
| resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Lit"); |
| resp.SetKeyInJsonBody(chassis_pointer / "LocationIndicatorActive", |
| true); |
| break; |
| case LED_STATE_BLINK: |
| resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Blinking"); |
| resp.SetKeyInJsonBody(chassis_pointer / "LocationIndicatorActive", |
| true); |
| break; |
| case LED_STATE_OFF: |
| resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Off"); |
| resp.SetKeyInJsonBody(chassis_pointer / "LocationIndicatorActive", |
| false); |
| break; |
| case LED_STATE_UNSPECIFIED: |
| default: |
| resp.SetKeyInJsonBody(chassis_pointer / "IndicatorLED", "Unknown"); |
| break; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| void FillResponseWithFruData( |
| const nlohmann::json::json_pointer& chassis_pointer, const Fru* fru_ptr, |
| const TopologyConfigNode* topology_config_node_ptr, |
| const ::milotic_tlbmc::Store& store, 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 for Root Chassis or Chassis with Custom Reset. |
| 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()) || |
| store.SupportsCustomChassisReset(chassis_id).value_or(false)) { |
| 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 / "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 / "Controls" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Controls"})); |
| |
| // Only populate Thermal if Thermal Control is disabled. |
| if (!GetTlbmcConfig() |
| .sensor_collector_module() |
| .thermal_control_sub_module() |
| .enabled()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Thermal" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Thermal"})); |
| } |
| |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "ThermalSubsystem" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "ThermalSubsystem"})); |
| |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Power" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "Power"})); |
| 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"})); |
| |
| if (GetTlbmcConfig() |
| .gpio_collector_module() |
| .power_control_sub_module() |
| .enabled()) { |
| if (auto* collector = store.GetPowerControlCollector(); |
| collector != nullptr) { |
| if (auto* power_control = collector->GetPowerControl(chassis_id); |
| power_control != nullptr) { |
| auto power_state = power_control->GetHostState().power_state(); |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "PowerState", |
| power_state == PowerState::POWER_STATE_ON ? "On" : "Off"); |
| } else { |
| LOG(INFO) << "Power control not found for chassis: " << chassis_id; |
| resp.SetKeyInJsonBody(chassis_pointer / "PowerState", "On"); |
| } |
| } else { |
| LOG(INFO) << "Power control collector not found"; |
| resp.SetKeyInJsonBody(chassis_pointer / "PowerState", "On"); |
| } |
| } else { |
| resp.SetKeyInJsonBody(chassis_pointer / "PowerState", "On"); |
| } |
| |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "PCIeSlots" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Chassis", chassis_id, "PCIeSlots"})); |
| |
| if (GetTlbmcConfig() |
| .fru_collector_module() |
| .expose_systems_child_links_in_chassis()) { |
| std::string system_id = GetSystemId(); |
| if (!system_id.empty()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Memory" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Systems", system_id, "Memory"})); |
| |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "PCIeDevices" / "@odata.id", |
| CreateUrl({"redfish", "v1", "Systems", system_id, "PCIeDevices"})); |
| } |
| } |
| |
| 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 (!asset_info.version().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Version", asset_info.version()); |
| } |
| if (!asset_info.asset_tag().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "AssetTag", |
| asset_info.asset_tag()); |
| } |
| } |
| |
| if (fru_ptr->data().has_fru_info() && |
| !fru_ptr->data().fru_info().mac_address().empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Oem" / "Google" / "MacAddress", |
| fru_ptr->data().fru_info().mac_address()); |
| } |
| |
| if (fru_ptr->attributes().chassis_properties().has_replaceable()) { |
| resp.SetKeyInJsonBody( |
| chassis_pointer / "Replaceable", |
| fru_ptr->attributes().chassis_properties().replaceable()); |
| } |
| |
| 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, |
| topology_config_node_ptr->children_system_ids()); |
| 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, |
| topology_config_node_ptr->children_system_ids()); |
| nlohmann::json::array_t parent_cables = CreateChildResourceLinksArray( |
| topology_config_node_ptr->parent_cable_ids(), RESOURCE_TYPE_CABLE, |
| topology_config_node_ptr->children_system_ids()); |
| nlohmann::json::array_t all_cables; |
| all_cables.insert(all_cables.end(), child_cables.begin(), child_cables.end()); |
| all_cables.insert(all_cables.end(), parent_cables.begin(), |
| parent_cables.end()); |
| if (!all_cables.empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables", all_cables); |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "Cables@odata.count", |
| all_cables.size()); |
| } |
| |
| // Processors are stored as a map to avoid conflicting processor ids on |
| // different chassis. We must iterate over the map to get all keys. |
| ::google::protobuf::RepeatedPtrField<std::string> processor_ids; |
| for (const auto& [processor_id, _] : |
| topology_config_node_ptr->children_processors()) { |
| processor_ids.Add(std::string(processor_id)); |
| } |
| std::sort(processor_ids.pointer_begin(), processor_ids.pointer_end(), |
| [](const std::string* id_1, const std::string* id_2) { |
| return *id_1 < *id_2; |
| }); |
| nlohmann::json::array_t child_processors = CreateChildResourceLinksArray( |
| processor_ids, RESOURCE_TYPE_PROCESSOR, |
| topology_config_node_ptr->children_system_ids()); |
| 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; |
| for (const auto& storage_info : |
| topology_config_node_ptr->children_storage_ids()) { |
| nlohmann::json::object_t child_object; |
| switch (storage_info.link_config().link_type()) { |
| case STORAGE_LINK_TYPE_ROOT: |
| child_object["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Storage", storage_info.id()}); |
| child_storages.push_back(std::move(child_object)); |
| break; |
| |
| // Default to the behavior of linking to storage under system. |
| case STORAGE_LINK_TYPE_SYSTEMS: |
| default: |
| if (topology_config_node_ptr->children_system_ids().empty()) { |
| // If no system ids are provided, use the default system id "system". |
| if (GetTlbmcConfig() |
| .fru_collector_module() |
| .expose_default_system_link()) { |
| child_object["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Systems", GetSystemId(), "Storage", |
| storage_info.id()}); |
| child_storages.push_back(std::move(child_object)); |
| } |
| } else { |
| for (const auto& system_id : |
| topology_config_node_ptr->children_system_ids()) { |
| nlohmann::json::object_t child_object; |
| child_object["@odata.id"] = |
| CreateUrl({"redfish", "v1", "Systems", system_id, "Storage", |
| storage_info.id()}); |
| child_storages.push_back(std::move(child_object)); |
| } |
| } |
| } |
| } |
| 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()})); |
| } |
| |
| PopulateManagedByLink(chassis_pointer, *topology_config_node_ptr, resp); |
| |
| nlohmann::json::array_t systems; |
| if (topology_config_node_ptr->children_system_ids().empty()) { |
| // Default system for backward compatibility. |
| if (GetTlbmcConfig().fru_collector_module().expose_default_system_link()) { |
| nlohmann::json::object_t system; |
| system["@odata.id"] = absl::StrCat("/redfish/v1/Systems/", GetSystemId()); |
| systems.push_back(system); |
| } |
| } else { |
| for (const auto& id : topology_config_node_ptr->children_system_ids()) { |
| nlohmann::json::object_t system; |
| system["@odata.id"] = CreateUrl({"redfish", "v1", "Systems", id}); |
| systems.push_back(system); |
| } |
| } |
| if (!systems.empty()) { |
| resp.SetKeyInJsonBody(chassis_pointer / "Links" / "ComputerSystems", |
| systems); |
| } |
| |
| InjectIndicatorLed(store, chassis_pointer, resp); |
| |
| resp.SetKeyInJsonBody(chassis_pointer / "Status" / "Health", "OK"); |
| resp.SetKeyInJsonBody(chassis_pointer / "Status" / "HealthRollup", "OK"); |
| resp.SetKeyInJsonBody(chassis_pointer / "Status" / "State", "Enabled"); |
| } |
| |
| absl::Status TriggerCustomChassisReset(Store& store, |
| absl::string_view chassis_id, |
| absl::string_view reset_type_str) { |
| ResetType reset_type = milotic_tlbmc::GetResetType(reset_type_str); |
| if (reset_type == ResetType::RESET_TYPE_UNSPECIFIED) { |
| DLOG(INFO) << "Unsupported reset type: " << reset_type_str; |
| return absl::InvalidArgumentError("Unsupported reset type"); |
| } |
| |
| absl::Status status = store.ExecuteCustomChassisReset(chassis_id, reset_type); |
| if (!status.ok()) { |
| DLOG(INFO) << "Failed custom chassis reset for " << chassis_id << ": " |
| << status; |
| } |
| return status; |
| } |
| |
| absl::Status TriggerDefaultRootChassisReset(Store& store, |
| absl::string_view chassis_id, |
| absl::string_view reset_type_str) { |
| absl::StatusOr<const TopologyConfig*> topology_config = |
| store.GetTopologyConfig(); |
| if (!topology_config.ok()) { |
| return absl::InternalError("Failed to get topology config"); |
| } |
| if (chassis_id != (*topology_config)->root_node_key()) { |
| return absl::InternalError( |
| "Chassis reset is only supported for root chassis"); |
| } |
| |
| if (reset_type_str != "PowerCycle") { |
| DLOG(INFO) << "Invalid reset type for root chassis: " << reset_type_str; |
| return absl::InternalError("Invalid reset type"); |
| } |
| |
| if (auto* collector = store.GetPowerControlCollector(); |
| collector != nullptr) { |
| collector->TriggerPrePowerCycleCallbacks(); |
| } |
| |
| absl::StatusOr<std::string> hotswap_gpio_name = store.GetHotswapGpioName(); |
| if (!hotswap_gpio_name.ok()) { |
| DLOG(INFO) << "Failed to get hotswap GPIO name: " |
| << hotswap_gpio_name.status(); |
| return hotswap_gpio_name.status(); |
| } |
| |
| absl::Status status = store.SetGpioActive(*hotswap_gpio_name); |
| if (!status.ok()) { |
| DLOG(INFO) << "Failed to power cycle chassis: " << chassis_id; |
| } |
| return status; |
| } |
| |
| void HandleChassisReset(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp, absl::string_view chassis_id) { |
| auto* store = app.GetStore(); |
| if (store == nullptr) { |
| resp.SetToInternalError("Store is null"); |
| return; |
| } |
| |
| // Consolidated request parsing (shared by both custom and default paths). |
| absl::string_view req_body = req.Body(); |
| DLOG(INFO) << "Request body: " << req_body; |
| nlohmann::json req_json = nlohmann::json::parse(req_body, nullptr, false); |
| if (req_json.is_discarded()) { |
| DLOG(INFO) << "Failed to parse request body: " << req_body; |
| resp.SetToBadRequest("Failed to parse request body"); |
| return; |
| } |
| |
| const std::string* reset_type_str = |
| ::milotic::authz::GetValueAsString(req_json, "ResetType"); |
| if (reset_type_str == nullptr) { |
| DLOG(INFO) << "Invalid or missing ResetType"; |
| resp.SetToBadRequest("Invalid reset type"); |
| return; |
| } |
| |
| absl::StatusOr<bool> supports_custom_reset = |
| store->SupportsCustomChassisReset(chassis_id); |
| if (!supports_custom_reset.ok()) { |
| resp.SetToAbslStatus(supports_custom_reset.status()); |
| return; |
| } |
| absl::Status status; |
| if (*supports_custom_reset) { |
| status = TriggerCustomChassisReset(*store, chassis_id, *reset_type_str); |
| } else { |
| status = |
| TriggerDefaultRootChassisReset(*store, chassis_id, *reset_type_str); |
| } |
| |
| if (!status.ok()) { |
| resp.SetToAbslStatus(status); |
| return; |
| } |
| |
| // Shared success response for both paths. |
| resp.SetToAccepted(absl::StrCat("/redfish/v1/Chassis/", chassis_id, |
| "/Actions/Chassis.Reset/")); |
| resp.SetKeyInJsonBody("/@Message.ExtendedInfo", |
| nlohmann::json::array_t({nlohmann::json::object_t{ |
| {"@odata.type", "#Message.v1_1_1.Message"}, |
| {"Message", "The request completed successfully."}, |
| {"MessageArgs", nlohmann::json::array_t()}, |
| {"MessageId", "Base.1.13.0.Success"}, |
| {"MessageSeverity", "OK"}, |
| {"Resolution", "None"}, |
| }})); |
| } |
| |
| void HandleChassisResetActionInfo(const RedfishApp& app, |
| const RedfishRequest& req, |
| RedfishResponse& resp, |
| absl::string_view chassis_id) { |
| const auto* store = app.GetStore(); |
| if (store == nullptr) { |
| resp.SetToInternalError("Store is null"); |
| return; |
| } |
| |
| resp.SetKeyInJsonBody( |
| "/@odata.id", |
| absl::StrCat("/redfish/v1/Chassis/", chassis_id, "/ResetActionInfo/")); |
| resp.SetKeyInJsonBody("/@odata.type", "#ActionInfo.v1_1_2.ActionInfo"); |
| resp.SetKeyInJsonBody("/Id", "ResetActionInfo"); |
| resp.SetKeyInJsonBody("/Name", "Reset Action Info"); |
| |
| nlohmann::json::array_t allowable_values; |
| absl::StatusOr<std::vector<ResetType>> supported_types = |
| store->GetSupportedCustomChassisResetTypes(chassis_id); |
| if (!supported_types.ok()) { |
| resp.SetToAbslStatus(supported_types.status()); |
| return; |
| } |
| for (ResetType reset_type : *supported_types) { |
| absl::StatusOr<absl::string_view> type_str = |
| milotic_tlbmc::GetResetTypeString(reset_type); |
| if (!type_str.ok()) { |
| LOG(WARNING) << "Failed to convert reset type to string: " |
| << type_str.status(); |
| continue; |
| } |
| allowable_values.push_back(std::string(*type_str)); |
| } |
| if (allowable_values.empty()) { |
| // Default for root chassis that has no custom reset configured. |
| absl::StatusOr<const TopologyConfig*> topology_config = |
| store->GetTopologyConfig(); |
| if (topology_config.ok() && |
| chassis_id == (*topology_config)->root_node_key()) { |
| allowable_values.push_back("PowerCycle"); |
| } |
| } |
| |
| nlohmann::json::array_t parameters = { |
| nlohmann::json::object_t{ |
| {"Name", "ResetType"}, |
| {"DataType", "String"}, |
| {"Required", true}, |
| {"AllowableValues", std::move(allowable_values)}}, |
| nlohmann::json::object_t{ |
| {"Name", "Delay"}, {"DataType", "Number"}, {"Required", false}}}; |
| resp.SetKeyInJsonBody("/Parameters", parameters); |
| } |
| |
| // Handles PATCH requests to update the indicator LED state on a Chassis |
| // resource. |
| void HandleChassisPatch(const RedfishApp& app, const RedfishRequest& req, |
| RedfishResponse& resp, |
| const std::string& /*chassis_id*/) { |
| const Store* store = app.GetStore(); |
| if (store == nullptr) { |
| resp.SetToInternalError("Store is null"); |
| return; |
| } |
| |
| LedCollector* led_collector = store->GetLedCollector(); |
| if (led_collector == nullptr) { |
| resp.SetToNoContent(); |
| return; |
| } |
| if (!led_collector->HasIndicatorConfig()) { |
| resp.SetToNoContent(); |
| return; |
| } |
| |
| nlohmann::json req_json = nlohmann::json::parse(req.Body(), nullptr, false); |
| if (req_json.is_discarded()) { |
| resp.SetToBadRequest("Failed to parse request body"); |
| return; |
| } |
| |
| const std::string* indicator_led = GetValueAsString(req_json, "IndicatorLED"); |
| const bool* location_indicator_active = |
| GetValueAsBool(req_json, "LocationIndicatorActive"); |
| |
| if (indicator_led == nullptr && location_indicator_active == nullptr) { |
| resp.SetToNoContent(); |
| return; |
| } |
| |
| absl::Status status = absl::OkStatus(); |
| if (indicator_led != nullptr) { |
| if (*indicator_led == "Lit") { |
| status = led_collector->SetIndicatorState(LED_STATE_ON); |
| } else if (*indicator_led == "Blinking") { |
| status = led_collector->SetIndicatorState(LED_STATE_BLINK); |
| } else if (*indicator_led == "Off") { |
| status = led_collector->SetIndicatorState(LED_STATE_OFF); |
| } else { |
| resp.SetToBadRequest("Invalid IndicatorLED value"); |
| return; |
| } |
| } else if (location_indicator_active != nullptr) { |
| if (*location_indicator_active) { |
| status = led_collector->SetIndicatorState(LED_STATE_BLINK); |
| } else { |
| status = led_collector->SetIndicatorState(LED_STATE_OFF); |
| } |
| } |
| |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to set indicator LED: " << status; |
| resp.SetToInternalError("Failed to set indicator LED"); |
| return; |
| } |
| |
| resp.SetToNoContent(); |
| } |
| |
| void RegisterRoutes(RedfishApp& app) { |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleChassis, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/") |
| .methods(boost::beast::http::verb::patch)( |
| absl::bind_front(HandleChassisPatch, std::cref(app))); |
| if (GetTlbmcConfig().fru_collector_module().enabled() && |
| GetTlbmcConfig() |
| .fru_collector_module() |
| .own_chassis_collection_in_redfish()) { |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleChassisCollection, std::cref(app))); |
| } |
| } |
| |
| void RegisterChassisResetRoutes(RedfishApp& app) { |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/") |
| .methods(boost::beast::http::verb::post)( |
| absl::bind_front(HandleChassisReset, std::cref(app))); |
| TLBMC_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/") |
| .methods(boost::beast::http::verb::get)( |
| absl::bind_front(HandleChassisResetActionInfo, std::cref(app))); |
| } |
| |
| } // namespace milotic_tlbmc::chassis |