blob: b3597ac52025812824dec0df69361a8331b2aeaa [file]
#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