| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_CHASSIS_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_CHASSIS_H_ | |
| /* | |
| // Copyright (c) 2018 Intel Corporation | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| */ | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <filesystem> // NOLINT | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <regex> // NOLINT | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "gbmc_settings/gbmc_settings.h" | |
| #include "absl/functional/any_invocable.h" // IWYU pragma: keep | |
| #include "absl/status/status.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "bmcweb_config.h" | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "chassis_utils.hpp" | |
| #include "collection.hpp" | |
| #include "dbus_utils.hpp" | |
| #include "json_utils.hpp" | |
| #include "location_utils.hpp" | |
| #include "storage_utils.hpp" | |
| #include "system_utils.hpp" | |
| #include "assembly.hpp" | |
| #include "health.hpp" | |
| #include "led.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| namespace redfish { | |
| constexpr int KMAX_RESET_DELAY_SEC = 3600; | |
| /** | |
| * @brief Retrieves resources over dbus to link to the chassis | |
| * | |
| * @param[in] asyncResp - Shared pointer for completing asynchronous | |
| * calls | |
| * @param[in] path - Chassis dbus path to look for the storage. | |
| * | |
| * Calls the Association endpoints on the path + "/storage" and add the link of | |
| * json["Links"]["Storage@odata.count"] = | |
| * {"@odata.id", "/redfish/v1/Storage/" + resourceId} | |
| * | |
| * @return None. | |
| */ | |
| inline void getStorageLinkFromChassis( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const sdbusplus::message::object_path& path) { | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::vector<std::string>>( | |
| "xyz.openbmc_project.ObjectMapper", (path / "storage").str, | |
| "xyz.openbmc_project.Association", "endpoints", context, | |
| [asyncResp](const boost::system::error_code ec, | |
| const std::vector<std::string>& storageList) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "getStorageLink got DBUS response error"; | |
| return; | |
| } | |
| nlohmann::json::array_t storages; | |
| asyncResp->res.jsonValue["Links"]["Storage@odata.count"] = | |
| storages.size(); | |
| asyncResp->res.jsonValue["Links"]["Storage"] = std::move(storages); | |
| for (const std::string& storagePath : storageList) { | |
| std::string id = | |
| sdbusplus::message::object_path(storagePath).filename(); | |
| if (id.empty()) { | |
| continue; | |
| } | |
| storage_utils::getSystemPathFromStorage( | |
| asyncResp, storagePath, | |
| [asyncResp, id](std::optional<std::string_view> systemPath) { | |
| std::string systemName = | |
| systemPath ? std::filesystem::path(*systemPath).filename() | |
| : "system"; | |
| asyncResp->res.jsonValue["Links"]["Storage@odata.count"] = | |
| asyncResp->res.jsonValue["Links"]["Storage@odata.count"] | |
| .get<std::size_t>() + | |
| 1; | |
| nlohmann::json::object_t storage; | |
| storage["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Systems", systemName, "Storage", id); | |
| asyncResp->res.jsonValue["Links"]["Storage"].emplace_back( | |
| std::move(storage)); | |
| }); | |
| } | |
| }); | |
| } | |
| /** | |
| * @brief Retrieves chassis state properties over dbus | |
| * | |
| * @param[in] aResp - Shared pointer for completing asynchronous calls. | |
| * | |
| * @return None. | |
| */ | |
| inline void getChassisState(std::shared_ptr<bmcweb::AsyncResp> aResp, | |
| std::string_view chassisId) { | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.State.Chassis"}; | |
| // Use mapper to get subtree paths. | |
| managedStore::ManagedObjectStoreContext requestContext(aResp); | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/", 0, interfaces, requestContext, | |
| [aResp{std::move(aResp)}, chassisId{std::string(chassisId)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& | |
| chassisList) mutable { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "[mapper] Bad D-Bus request error: " << ec; | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| std::string objectPath = "/xyz/openbmc_project/state/chassis0"; | |
| std::string service = "xyz.openbmc_project.State.Chassis"; | |
| /* If it is multi-chassis, determine the object path. */ | |
| if (!chassisId.empty()) { | |
| size_t lastNonDigit = chassisId.find_last_not_of("0123456789"); | |
| std::string numericPart = (lastNonDigit == std::string::npos) | |
| ? chassisId | |
| : chassisId.substr(lastNonDigit + 1); | |
| if (!numericPart.empty()) { | |
| objectPath = | |
| absl::StrCat("/xyz/openbmc_project/state/chassis", numericPart); | |
| service = | |
| absl::StrCat("xyz.openbmc_project.State.Chassis", numericPart); | |
| } else { | |
| BMCWEB_LOG_DEBUG << "The numericPart for chassisId is empty; using " | |
| "the default objectPath and service"; | |
| } | |
| } | |
| /* Look for system reset chassis path */ | |
| if ((std::find(chassisList.begin(), chassisList.end(), objectPath)) == | |
| chassisList.end()) { | |
| /* | |
| * The default on systems is chassis0 for the power state. | |
| * On systems that dont have chassis0, we can use chassis_system0 | |
| */ | |
| objectPath = "/xyz/openbmc_project/state/chassis_system0"; | |
| } | |
| managedStore::ManagedObjectStoreContext context(aResp); | |
| dbus_utils::getProperty<std::string>( | |
| service, objectPath, "xyz.openbmc_project.State.Chassis", | |
| "CurrentPowerState", context, | |
| [aResp{std::move(aResp)}](const boost::system::error_code& ec2, | |
| const std::string& chassisState) { | |
| if (ec2) { | |
| if (ec2 == boost::system::errc::host_unreachable) { | |
| // Service not available, no error, just don't return | |
| // chassis state info | |
| BMCWEB_LOG_DEBUG << "Service not available " << ec2; | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec2; | |
| messages::internalError(aResp->res); | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Chassis state: " << chassisState; | |
| // Verify Chassis State | |
| if (chassisState == | |
| "xyz.openbmc_project.State.Chassis.PowerState.On") { | |
| aResp->res.jsonValue["PowerState"] = "On"; | |
| aResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| } else if (chassisState == | |
| "xyz.openbmc_project.State.Chassis.PowerState.Off") { | |
| aResp->res.jsonValue["PowerState"] = "Off"; | |
| aResp->res.jsonValue["Status"]["State"] = "StandbyOffline"; | |
| } | |
| }); | |
| }); | |
| } | |
| inline void getIntrusionByService(std::shared_ptr<bmcweb::AsyncResp> aResp, | |
| const std::string& service, | |
| const std::string& objPath) { | |
| BMCWEB_LOG_DEBUG << "Get intrusion status by service \n"; | |
| managedStore::ManagedObjectStoreContext context(aResp); | |
| dbus_utils::getProperty<std::string>( | |
| service, objPath, "xyz.openbmc_project.Chassis.Intrusion", "Status", | |
| context, | |
| [aResp{std::move(aResp)}](const boost::system::error_code& ec, | |
| const std::string& value) { | |
| if (ec) { | |
| // do not add err msg in redfish response, because this is not | |
| // mandatory property | |
| BMCWEB_LOG_ERROR << "DBUS response error " << ec << "\n"; | |
| return; | |
| } | |
| aResp->res.jsonValue["PhysicalSecurity"]["IntrusionSensorNumber"] = 1; | |
| aResp->res.jsonValue["PhysicalSecurity"]["IntrusionSensor"] = value; | |
| }); | |
| } | |
| /** | |
| * Retrieves physical security properties over dbus | |
| */ | |
| inline void getPhysicalSecurityData(std::shared_ptr<bmcweb::AsyncResp> aResp) { | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.Chassis.Intrusion"}; | |
| managedStore::ManagedObjectStoreContext requestContext(aResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project/Intrusion", 1, interfaces, requestContext, | |
| [aResp{std::move(aResp)}]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| // do not add err msg in redfish response, because this is not | |
| // mandatory property | |
| BMCWEB_LOG_INFO << "DBUS error: no matched iface " << ec << "\n"; | |
| return; | |
| } | |
| // Iterate over all retrieved ObjectPaths. | |
| for (const auto& object : subtree) { | |
| for (const auto& service : object.second) { // NOLINT | |
| getIntrusionByService(aResp, service.first, object.first); | |
| return; | |
| } | |
| } | |
| }); | |
| } | |
| inline void handleChassisCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#ChassisCollection.ChassisCollection"; | |
| asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Chassis"; | |
| asyncResp->res.jsonValue["Name"] = "Chassis Collection"; | |
| constexpr std::array<std::string_view, 2> interfaces{ | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; | |
| collection_util::getCollectionMembers( | |
| asyncResp, boost::urls::url("/redfish/v1/Chassis"), interfaces); | |
| } | |
| inline nlohmann::json& getChassisContainsArray( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Contains"]; | |
| if (!jValue.is_array()) { | |
| jValue = nlohmann::json::array(); | |
| asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size(); | |
| } | |
| return jValue; | |
| } | |
| // Hack in the Contains relationships | |
| inline void addHardcodedRelationships( | |
| const std::string& chassisId, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if constexpr (bmcwebEnableRdeDevice) { | |
| if (chassisId == platform6Chassis0) { | |
| nlohmann::json& jValue = getChassisContainsArray(asyncResp); | |
| const std::vector<std::string> backplaneTrays = { | |
| "2HMMCS_Tray", | |
| }; | |
| for (const std::string& tray : backplaneTrays) { | |
| jValue.emplace_back( | |
| nlohmann::json({{"@odata.id", "/redfish/v1/Chassis/" + tray}})); | |
| } | |
| asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = jValue.size(); | |
| } | |
| } | |
| } | |
| // End Hack | |
| inline void getChassisContainedBy( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const boost::system::error_code& ec, | |
| const dbus::utility::MapperEndPoints& upstreamChassisPaths) { | |
| if (ec) { | |
| if (ec.value() != EBADR) { | |
| BMCWEB_LOG_ERROR << "DBUS response error " << ec; | |
| messages::internalError(asyncResp->res); | |
| } | |
| return; | |
| } | |
| if (upstreamChassisPaths.empty()) { | |
| return; | |
| } | |
| if (upstreamChassisPaths.size() > 1) { | |
| BMCWEB_LOG_ERROR << chassisId << " is contained by mutliple chassis"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| sdbusplus::message::object_path upstreamChassisPath(upstreamChassisPaths[0]); | |
| std::string upstreamChassis = upstreamChassisPath.filename(); | |
| if (upstreamChassis.empty()) { | |
| BMCWEB_LOG_WARNING << "Malformed upstream Chassis path " | |
| << upstreamChassisPath.str << " on " << chassisId; | |
| return; | |
| } | |
| asyncResp->res.jsonValue["Links"]["ContainedBy"]["@odata.id"] = | |
| "/redfish/v1/Chassis/" + upstreamChassis; | |
| } | |
| inline void getChassisContains( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const boost::system::error_code& ec, | |
| const dbus::utility::MapperEndPoints& downstreamChassisPaths) { | |
| if (ec) { | |
| if (ec.value() != EBADR) { | |
| BMCWEB_LOG_ERROR << "DBUS response error " << ec; | |
| messages::internalError(asyncResp->res); | |
| } | |
| return; | |
| } | |
| if (downstreamChassisPaths.empty()) { | |
| return; | |
| } | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| for (const auto& chassisPath : downstreamChassisPaths) { | |
| sdbusplus::message::object_path downstreamChassisPath(chassisPath); | |
| const std::string downstreamChassis = downstreamChassisPath.filename(); | |
| if (downstreamChassis.empty()) { | |
| BMCWEB_LOG_WARNING << "Malformed downstream Chassis path " | |
| << downstreamChassisPath.str << " on " << chassisId; | |
| continue; | |
| } | |
| managedStore::GetManagedObjectStore()->getDbusObject( | |
| chassisPath, {}, requestContext, | |
| [asyncResp, downstreamChassis]( | |
| const boost::system::error_code& ec2, | |
| const dbus::utility::MapperGetObject& object) { | |
| if (ec2) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // Link must be a Chassis or Board | |
| constexpr std::array<std::string_view, 2> chassisInterfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Chassis", | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| }; | |
| if (!redfish::dbus_utils::findInterfacesInServiceMap( | |
| object, chassisInterfaces)) { | |
| return; | |
| } | |
| nlohmann::json& jValue = | |
| asyncResp->res.jsonValue["Links"]["Contains"]; | |
| if (!jValue.is_array()) { | |
| // Create the array if it was empty | |
| jValue = nlohmann::json::array(); | |
| } | |
| nlohmann::json link; | |
| link["@odata.id"] = "/redfish/v1/Chassis/" + downstreamChassis; | |
| jValue.emplace_back(std::move(link)); | |
| asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = | |
| jValue.size(); | |
| }); | |
| } | |
| } | |
| inline void getChassisConnectivity( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const std::string& chassisPath) { | |
| BMCWEB_LOG_DEBUG << "Get chassis connectivity"; | |
| if constexpr (bmcwebEnableRdeDevice) { | |
| // Hack in Chassis links | |
| addHardcodedRelationships(chassisId, asyncResp); | |
| // End Hack | |
| } | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| // Chassis Dbus interfaces. They are the same types when we enumerate the | |
| // Chassis Collection. | |
| constexpr std::array<std::string_view, 2> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; | |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | |
| chassisPath + "/contained_by", {"/xyz/openbmc_project/inventory"}, 0, | |
| interfaces, requestContext, | |
| std::bind_front(getChassisContainedBy, asyncResp, chassisId)); | |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | |
| chassisPath + "/containing", {"/xyz/openbmc_project/inventory"}, 0, | |
| interfaces, requestContext, | |
| std::bind_front(getChassisContains, asyncResp, chassisId)); | |
| } | |
| inline void getChassisCableAssociation( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisPath) { | |
| BMCWEB_LOG_DEBUG << "Get chassis -- cable association"; | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::vector<std::string>>( | |
| "xyz.openbmc_project.ObjectMapper", chassisPath + "/attached_cables", | |
| "xyz.openbmc_project.Association", "endpoints", context, | |
| [asyncResp](const boost::system::error_code ec, | |
| const std::vector<std::string>& cableList) { | |
| if (ec) { | |
| return; | |
| } | |
| if (cableList.empty()) { | |
| return; | |
| } | |
| nlohmann::json& jValue = asyncResp->res.jsonValue["Links"]["Cables"]; | |
| jValue = nlohmann::json::array(); | |
| for (const std::string& cable : cableList) { | |
| sdbusplus::message::object_path cablePath(cable); | |
| std::string cableName = cablePath.filename(); | |
| if (cable.empty()) { | |
| BMCWEB_LOG_ERROR << "filename() is empty in " << cable; | |
| continue; | |
| } | |
| jValue.emplace_back(nlohmann::json( | |
| {{"@odata.id", "/redfish/v1/Cables/" + cableName}})); | |
| } | |
| asyncResp->res.jsonValue["Links"]["Cables@odata.count"] = | |
| cableList.size(); | |
| }); | |
| } | |
| inline void getChassisProcessorAssociation( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisPath, bool multiHost) { | |
| BMCWEB_LOG_DEBUG << "Get chassis -- processor association"; | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::vector<std::string>>( | |
| "xyz.openbmc_project.ObjectMapper", chassisPath + "/processors", | |
| "xyz.openbmc_project.Association", "endpoints", context, | |
| [asyncResp, multiHost](const boost::system::error_code ec, | |
| const std::vector<std::string>& processors) { | |
| if (ec) { | |
| return; | |
| } | |
| nlohmann::json& jValue = | |
| asyncResp->res.jsonValue["Links"]["Processors"]; | |
| jValue = nlohmann::json::array(); | |
| for (const std::string& processor : processors) { | |
| sdbusplus::message::object_path processorPath(processor); | |
| std::string processorName = processorPath.filename(); | |
| if (processorName.empty()) { | |
| BMCWEB_LOG_ERROR << "filename() is empty in " << processor; | |
| continue; | |
| } | |
| // Special way of determining processor path for single vs multihost | |
| if (!multiHost) { | |
| jValue.emplace_back(nlohmann::json( | |
| {{"@odata.id", | |
| "/redfish/v1/Systems/system/Processors/" + processorName}})); | |
| continue; | |
| } | |
| // The 3rd parent is the systemId for processor in multihost machines | |
| std::string systemId = processorPath.parent_path() | |
| .parent_path() | |
| .parent_path() | |
| .filename(); | |
| jValue.emplace_back(nlohmann::json( | |
| {{"@odata.id", crow::utility::urlFromPieces( | |
| "redfish", "v1", "Systems", systemId, | |
| "Processors", processorName)}})); | |
| } | |
| std::sort(jValue.begin(), jValue.end()); | |
| asyncResp->res.jsonValue["Links"]["Processors@odata.count"] = | |
| processors.size(); | |
| }); | |
| } | |
| inline void getChassisMemoryAssociationSingleSystem( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisPath) { | |
| BMCWEB_LOG_DEBUG << "Get chassis -- memory association"; | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::vector<std::string>>( | |
| "xyz.openbmc_project.ObjectMapper", chassisPath + "/memories", | |
| "xyz.openbmc_project.Association", "endpoints", context, | |
| [asyncResp](const boost::system::error_code ec, | |
| const std::vector<std::string>& memories) { | |
| if (ec) { | |
| return; | |
| } | |
| if (memories.empty()) { | |
| return; | |
| } | |
| /* Redfish Chassis schema uses memory collection here. Only | |
| * motherboard would have such association. Hardcode the system | |
| * memory collection odata.id | |
| */ | |
| asyncResp->res.jsonValue["Memory"] = { | |
| {"@odata.id", "/redfish/v1/Systems/system/Memory"}}; | |
| }); | |
| } | |
| /** | |
| * Handles System related properties | |
| * | |
| * * On single-system system, a chassis will be default to the | |
| * only system w/ or w/o dedicated association. | |
| * | |
| * * On multi-system system, a chassis requires an association to | |
| * the target system or otherwise the chassis will be linked to all | |
| * systems on the system | |
| */ | |
| inline void getChassisSystem( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisPath) { | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/", 0, redfish::system_utils::systemInterfaces, requestContext, | |
| [asyncResp, chassisPath](const boost::system::error_code& ec, | |
| const std::vector<std::string>& objects) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (system_utils::isHardcodedSingleHost(objects.size())) { | |
| // Perform SingleSystem Only Actions | |
| getChassisMemoryAssociationSingleSystem(asyncResp, chassisPath); | |
| getChassisProcessorAssociation(asyncResp, chassisPath, false); | |
| asyncResp->res.jsonValue["PCIeDevices"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Systems", "system", | |
| "PCIeDevices"); | |
| nlohmann::json::object_t system; | |
| system["@odata.id"] = "/redfish/v1/Systems/system"; | |
| asyncResp->res.jsonValue["Links"]["ComputerSystems"].emplace_back( | |
| system); | |
| return; | |
| } | |
| // Otherwise find chassis -> system association | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | |
| chassisPath + "/contained_by", {"/xyz/openbmc_project/inventory"}, | |
| 0, system_utils::systemInterfaces, requestContext, | |
| [asyncResp, systemPaths{objects} | |
| // default the systemPath to all systems | |
| ](const boost::system::error_code& ec, | |
| const std::vector<std::string>& assocPaths) mutable { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error on getting " | |
| "association for system/chassis"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // override the default w/ associated systems | |
| if (!assocPaths.empty()) { | |
| systemPaths = assocPaths; | |
| } | |
| for_each(systemPaths.begin(), systemPaths.end(), | |
| [asyncResp](const std::string& path) { | |
| nlohmann::json::object_t system; | |
| system["@odata.id"] = | |
| std::filesystem::path("/redfish/v1/Systems") / | |
| std::filesystem::path(path).filename(); | |
| asyncResp->res.jsonValue["Links"]["ComputerSystems"] | |
| .emplace_back(system); | |
| }); | |
| }); | |
| // Perform mulithost only actions | |
| getChassisProcessorAssociation(asyncResp, chassisPath, true); | |
| }); | |
| } | |
| /** | |
| * ChassisCollection derived class for delivering Chassis Collection Schema | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| inline void requestRoutesChassisCollection(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/") | |
| .privileges(redfish::privileges::getChassisCollection) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleChassisCollectionGet, std::ref(app))); | |
| } | |
| inline std::optional<std::string> getChassisTypeProperty( | |
| const std::variant<std::string>& property) { | |
| const std::string* value = std::get_if<std::string>(&property); | |
| if (value == nullptr) { | |
| return std::nullopt; | |
| } | |
| if (*value == | |
| "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Component") { | |
| return "Component"; | |
| } | |
| if (*value == | |
| "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Enclosure") { | |
| return "Enclosure"; | |
| } | |
| if (*value == | |
| "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Module") { | |
| return "Module"; | |
| } | |
| if (*value == | |
| "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.RackMount") { | |
| return "RackMount"; | |
| } | |
| if (*value == | |
| "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType." | |
| "StandAlone") { | |
| return "StandAlone"; | |
| } | |
| if (*value == | |
| "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType." | |
| "StorageEnclosure") { | |
| return "StorageEnclosure"; | |
| } | |
| if (*value == "xyz.openbmc_project.Inventory.Item.Chassis.ChassisType.Zone") { | |
| return "Zone"; | |
| } | |
| return std::nullopt; | |
| } | |
| inline void addChassisReset(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, | |
| const std::optional<std::string>& chassisType) { | |
| // If chassisType is nullopt, then use the deault type of RackMount. | |
| if (chassisType) { | |
| asyncResp->res.jsonValue["ChassisType"] = *chassisType; | |
| } | |
| // Only Default, RackMount, or StandAlone will have Reset Action. | |
| if (chassisType && *chassisType != "RackMount" && | |
| *chassisType != "StandAlone") { | |
| BMCWEB_LOG_INFO << "Chassis Type is not supported. Only support Chassis " | |
| "Reset on RackMount or StandAlone."; | |
| return; | |
| } | |
| asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"]["target"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, | |
| "Actions", "Chassis.Reset"); | |
| asyncResp->res.jsonValue["Actions"]["#Chassis.Reset"]["@Redfish.ActionInfo"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId, | |
| "ResetActionInfo"); | |
| } | |
| inline void getChassisType( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& connectionName, const std::string& path, | |
| std::function<void(const boost::system::error_code ec, | |
| const std::variant<std::string>&)>&& callback) { | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| connectionName, path, "xyz.openbmc_project.Inventory.Item.Chassis", | |
| "Type", context, | |
| [callback{std::move(callback)}](boost::system::error_code ec, | |
| const std::string& val) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG | |
| << "Failed to get Chassis Type. Set ec to 0 in callback."; | |
| callback(boost::system::error_code(), std::variant<std::string>{""}); | |
| return; | |
| } | |
| callback(ec, val); | |
| }); | |
| } | |
| inline void getChassisLocationCode( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& connectionName, const std::string& path) { | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| connectionName, path, | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", "LocationCode", | |
| context, | |
| [asyncResp](const boost::system::error_code ec, | |
| const std::string& property) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error for Location"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = | |
| property; | |
| }); | |
| } | |
| inline void getChassisUUID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& connectionName, | |
| const std::string& path) { | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| dbus_utils::getProperty<std::string>( | |
| connectionName, path, "xyz.openbmc_project.Common.UUID", "UUID", context, | |
| [asyncResp](const boost::system::error_code& ec, | |
| const std::string& chassisUUID) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error for UUID"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["UUID"] = chassisUUID; | |
| }); | |
| } | |
| inline void handleChassisGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| constexpr std::array<std::string_view, 2> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | |
| [asyncResp, chassisId(std::string(chassisId)), requestContext]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // Iterate over all retrieved ObjectPaths. | |
| for (const std::pair< | |
| std::string, | |
| std::vector<std::pair<std::string, std::vector<std::string>>>>& | |
| object : subtree) { | |
| const std::string& path = object.first; | |
| const std::vector<std::pair<std::string, std::vector<std::string>>>& | |
| connectionNames = object.second; | |
| sdbusplus::message::object_path objPath(path); | |
| if (objPath.filename() != chassisId) { | |
| continue; | |
| } | |
| getChassisConnectivity(asyncResp, chassisId, path); | |
| getChassisCableAssociation(asyncResp, path); | |
| getChassisSystem(asyncResp, path); | |
| #ifdef HEALTH_POPULATE | |
| auto health = std::make_shared<HealthPopulate>( | |
| asyncResp, "/redfish/v1/Chassis/" + chassisId, path, "Board", | |
| chassisId); | |
| dbus_utils::getAssociationEndPoints( | |
| path + "/all_sensors", requestContext, | |
| [health](const boost::system::error_code& ec2, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| if (ec2) { | |
| return; // no sensors = no failures | |
| } | |
| health->inventory = resp; | |
| }); | |
| health->populate(); | |
| #else /* HEALTH_POPULATE */ | |
| auto health = std::make_shared<HealthPopulate>(asyncResp); | |
| #endif /* HEALTH_POPULATE */ | |
| if (connectionNames.empty()) { | |
| BMCWEB_LOG_ERROR << "Got 0 Connection names"; | |
| continue; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = "#Chassis.v1_17_0.Chassis"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId); | |
| asyncResp->res.jsonValue["Name"] = "Chassis Collection"; | |
| asyncResp->res.jsonValue["ChassisType"] = "RackMount"; | |
| asyncResp->res.jsonValue["Certificates"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "Certificates"); | |
| asyncResp->res.jsonValue["TrustedComponents"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "TrustedComponents"); | |
| dbus_utils::getAssociationEndPoints( | |
| path + "/drive", requestContext, | |
| [asyncResp, chassisId]( | |
| const boost::system::error_code& ec3, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| if (ec3 || resp.empty()) { | |
| return; // no drives = no failures | |
| } | |
| nlohmann::json reference; | |
| reference["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "Drives"); | |
| asyncResp->res.jsonValue["Drives"] = std::move(reference); | |
| }); | |
| dbus_utils::getProperty<std::string>( | |
| "xyz.openbmc_project.EntityManager", | |
| "/xyz/openbmc_project/inventory/system/board/" + chassisId + | |
| "/BmcNet", | |
| "xyz.openbmc_project.Configuration.BmcNet", "Name", | |
| requestContext, | |
| [asyncResp, path, chassisId](const boost::system::error_code& ec3, | |
| const std::string& bmcNetName) { | |
| if (ec3 || bmcNetName != "BmcNet") { | |
| managedStore::ManagedObjectStoreContext requestContextInner( | |
| asyncResp); | |
| // non-bmc chassis | |
| dbus_utils::getAssociationEndPoints( | |
| path + "/network_adapter", requestContextInner, | |
| [asyncResp, chassisId]( | |
| const boost::system::error_code& ec4, | |
| const dbus::utility::MapperEndPoints& resp) { | |
| if (ec4 || resp.empty()) { | |
| return; // no networkadapters = no failures | |
| } | |
| nlohmann::json reference; | |
| reference["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, | |
| "NetworkAdapters"); | |
| asyncResp->res.jsonValue["NetworkAdapters"] = | |
| std::move(reference); | |
| }); | |
| return; | |
| } | |
| // bmc chassis | |
| nlohmann::json reference; | |
| reference["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "NetworkAdapters"); | |
| asyncResp->res.jsonValue["NetworkAdapters"] = | |
| std::move(reference); | |
| }); | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| const std::string& connectionName = connectionNames[0].first; | |
| const std::vector<std::string>& interfaces2 = | |
| connectionNames[0].second; | |
| const std::array<const char*, 2> hasIndicatorLed = { | |
| "xyz.openbmc_project.Inventory.Item.Panel", | |
| "xyz.openbmc_project.Inventory.Item.Board.Motherboard"}; | |
| const std::string assetTagInterface = | |
| "xyz.openbmc_project.Inventory.Decorator.AssetTag"; | |
| const std::string revisionInterface = | |
| "xyz.openbmc_project.Inventory.Decorator.Revision"; | |
| for (const auto& interface : interfaces2) { | |
| if (interface == assetTagInterface) { | |
| dbus_utils::getProperty<std::string>( | |
| connectionName, path, assetTagInterface, "AssetTag", context, | |
| [asyncResp, chassisId(std::string(chassisId))]( | |
| const boost::system::error_code& ec2, | |
| const std::string& property) { | |
| if (ec2) { | |
| BMCWEB_LOG_DEBUG << "DBus response error for AssetTag"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["AssetTag"] = property; | |
| }); | |
| } else if (interface == revisionInterface) { | |
| dbus_utils::getProperty<std::string>( | |
| connectionName, path, revisionInterface, "Version", context, | |
| [asyncResp, chassisId](const boost::system::error_code& ec2, | |
| const std::string& property) { | |
| if (ec2) { | |
| BMCWEB_LOG_ERROR << "DBus response error for Version: " | |
| << ec2; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["Version"] = property; | |
| }); | |
| } | |
| } | |
| for (const char* interface : hasIndicatorLed) { | |
| if (std::find(interfaces2.begin(), interfaces2.end(), interface) != | |
| interfaces2.end()) { | |
| getIndicatorLedState(asyncResp); | |
| getLocationIndicatorActive(asyncResp); | |
| break; | |
| } | |
| } | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| connectionName, path, | |
| "xyz.openbmc_project.Inventory.Decorator.Asset", context, | |
| [asyncResp, chassisId(std::string(chassisId)), path]( | |
| const boost::system::error_code& /*ec2*/, | |
| const dbus::utility::DBusPropertiesMap& propertiesList) { | |
| const std::string* partNumber = nullptr; | |
| const std::string* serialNumber = nullptr; | |
| const std::string* manufacturer = nullptr; | |
| const std::string* model = nullptr; | |
| const std::string* sparePartNumber = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| dbus_utils::UnpackErrorPrinter(), propertiesList, | |
| "PartNumber", partNumber, "SerialNumber", serialNumber, | |
| "Manufacturer", manufacturer, "Model", model, | |
| "SparePartNumber", sparePartNumber); | |
| if (!success) { | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (partNumber != nullptr) { | |
| asyncResp->res.jsonValue["PartNumber"] = *partNumber; | |
| } | |
| if (serialNumber != nullptr) { | |
| asyncResp->res.jsonValue["SerialNumber"] = *serialNumber; | |
| } | |
| if (manufacturer != nullptr) { | |
| asyncResp->res.jsonValue["Manufacturer"] = *manufacturer; | |
| } | |
| if (model != nullptr) { | |
| asyncResp->res.jsonValue["Model"] = *model; | |
| } | |
| // SparePartNumber is optional on D-Bus | |
| // so skip if it is empty | |
| if (sparePartNumber != nullptr && !sparePartNumber->empty()) { | |
| asyncResp->res.jsonValue["SparePartNumber"] = | |
| *sparePartNumber; | |
| } | |
| if constexpr (bmcwebEnableRdeDevice) { | |
| // Hack in the ContainedBy fields | |
| const std::regex rdeTrayRegex(".*Tray"); | |
| if (std::regex_match(chassisId, rdeTrayRegex)) { | |
| // TODO(b/273682625): platform6Chassis1 Trays are cabled | |
| // instead of contained | |
| asyncResp->res.jsonValue["Links"]["ContainedBy"] = { | |
| {"@odata.id", "/redfish/v1/Chassis/" + | |
| std::string(platform6Chassis0)}}; | |
| } | |
| // End Hack | |
| } | |
| if constexpr (enablePlatform9) { | |
| // Hack in the Cables | |
| if (chassisId == platform9Chassis2) { | |
| nlohmann::json::array_t attachedCables; | |
| nlohmann::json::object_t cab; | |
| cab["@odata.id"] = | |
| "/redfish/v1/Cables/" + std::string(platform9Cable4); | |
| attachedCables.emplace_back(std::move(cab)); | |
| asyncResp->res.jsonValue["Links"]["Cables"] = | |
| std::move(attachedCables); | |
| } | |
| if (chassisId == platform9Chassis0) { | |
| nlohmann::json::array_t attachedCables; | |
| nlohmann::json::object_t cab; | |
| nlohmann::json::object_t cat; | |
| cat["@odata.id"] = "/redfish/v1/Cables/cat5_cable"; | |
| attachedCables.emplace_back(std::move(cat)); | |
| cab["@odata.id"] = | |
| "/redfish/v1/Cables/" + std::string(platform9Cable0); | |
| attachedCables.emplace_back(std::move(cab)); | |
| asyncResp->res.jsonValue["Links"]["Cables"] = | |
| std::move(attachedCables); | |
| } | |
| if (chassisId == platform9Chassis3) { | |
| nlohmann::json::array_t attachedCables; | |
| nlohmann::json::object_t cab; | |
| nlohmann::json::object_t cab1; | |
| cab["@odata.id"] = | |
| "/redfish/v1/Cables/" + std::string(platform9Cable2); | |
| cab1["@odata.id"] = | |
| "/redfish/v1/Cables/" + std::string(platform9Cable3); | |
| attachedCables.emplace_back(std::move(cab)); | |
| attachedCables.emplace_back(std::move(cab1)); | |
| asyncResp->res.jsonValue["Links"]["Cables"] = | |
| std::move(attachedCables); | |
| } | |
| // Hack in containing satellite Chassis | |
| if (chassisId == platform9Chassis4) { | |
| nlohmann::json& jValue = | |
| asyncResp->res.jsonValue["Links"]["Contains"]; | |
| jValue = nlohmann::json::array(); | |
| jValue.emplace_back(nlohmann::json( | |
| {{"@odata.id", "/redfish/v1/Chassis/5B247A_" + | |
| std::string(platform9Chassis5)}})); | |
| asyncResp->res.jsonValue["Links"]["Contains@odata.count"] = | |
| jValue.size(); | |
| } | |
| // End of workarounds | |
| } | |
| asyncResp->res.jsonValue["Name"] = chassisId; | |
| asyncResp->res.jsonValue["Id"] = chassisId; | |
| #ifdef BMCWEB_ALLOW_DEPRECATED_POWER_THERMAL | |
| asyncResp->res.jsonValue["Thermal"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "Thermal"); | |
| // Power object | |
| asyncResp->res.jsonValue["Power"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "Power"); | |
| #endif | |
| asyncResp->res.jsonValue["Assembly"] = { | |
| {"@odata.id", | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "Assembly") | |
| .buffer()}}; | |
| #ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM | |
| asyncResp->res.jsonValue["ThermalSubsystem"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "ThermalSubsystem"); | |
| asyncResp->res.jsonValue["PowerSubsystem"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "PowerSubsystem"); | |
| asyncResp->res.jsonValue["EnvironmentMetrics"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, | |
| "EnvironmentMetrics"); | |
| #endif | |
| // SensorCollection | |
| asyncResp->res.jsonValue["Sensors"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "Sensors"); | |
| asyncResp->res.jsonValue["Controls"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "Controls"); | |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| asyncResp->res.jsonValue["PCIeSlots"]["@odata.id"] = | |
| crow::utility::urlFromPieces("redfish", "v1", "Chassis", | |
| chassisId, "PCIeSlots"); | |
| nlohmann::json::array_t managedBy; | |
| nlohmann::json::object_t manager; | |
| manager["@odata.id"] = "/redfish/v1/Managers/bmc"; | |
| managedBy.emplace_back(std::move(manager)); | |
| asyncResp->res.jsonValue["Links"]["ManagedBy"] = | |
| std::move(managedBy); | |
| getChassisState(asyncResp, chassisId); | |
| getStorageLinkFromChassis(asyncResp, path); | |
| }); | |
| bool hasChassisInterface = false; | |
| for (const auto& interface : interfaces2) { | |
| if (interface == "xyz.openbmc_project.Common.UUID") { | |
| getChassisUUID(asyncResp, connectionName, path); | |
| } else if (interface == | |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode") { | |
| location_util::getLocationCode(asyncResp, connectionName, path, | |
| "/Location"_json_pointer); | |
| } else if (location_util::isConnector(interface)) { | |
| std::optional<std::string> locationType = | |
| location_util::getLocationType(interface); | |
| if (!locationType) { | |
| BMCWEB_LOG_DEBUG << "getLocationType for Chassis failed for " | |
| << interface; | |
| continue; | |
| } | |
| asyncResp->res | |
| .jsonValue["Location"]["PartLocation"]["LocationType"] = | |
| *locationType; | |
| } else if (interface == | |
| "xyz.openbmc_project.Inventory.Item.Chassis" || | |
| interface == | |
| "xyz.openbmc_project.Inventory.Item.Board") { | |
| hasChassisInterface = true; | |
| getChassisType(asyncResp, connectionName, path, | |
| [asyncResp, chassisId]( | |
| const boost::system::error_code ec2, | |
| const std::variant<std::string>& property) { | |
| if (ec2) { | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| addChassisReset( | |
| asyncResp, chassisId, | |
| getChassisTypeProperty(property)); | |
| }); | |
| } | |
| } | |
| if (hasChassisInterface) { | |
| location_util::getPartLocationContext( | |
| asyncResp, "/Location"_json_pointer, path + "/contained_by"); | |
| } else { | |
| location_util::getPartLocationContext( | |
| asyncResp, "/Location"_json_pointer, path + "/chassis"); | |
| // Item.Chassis interface is not available | |
| // Enable Reset Action by default. | |
| addChassisReset(asyncResp, chassisId, std::nullopt); | |
| } | |
| return; | |
| } | |
| // Couldn't find an object with that name. return an error | |
| messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); | |
| }); | |
| getPhysicalSecurityData(asyncResp); | |
| } | |
| inline void handleChassisPatch( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& param) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| std::optional<bool> locationIndicatorActive; | |
| std::optional<std::string> indicatorLed; | |
| if (param.empty()) { | |
| return; | |
| } | |
| if (!json_util::readJsonPatch(req, asyncResp->res, "LocationIndicatorActive", | |
| locationIndicatorActive, "IndicatorLED", | |
| indicatorLed)) { | |
| return; | |
| } | |
| // TODO (Gunnar): Remove IndicatorLED after enough time has passed | |
| if (!locationIndicatorActive && !indicatorLed) { | |
| return; // delete this when we support more patch properties | |
| } | |
| if (indicatorLed) { | |
| asyncResp->res.addHeader(boost::beast::http::field::warning, | |
| "299 - \"IndicatorLED is deprecated. Use " | |
| "LocationIndicatorActive instead.\""); | |
| } | |
| constexpr std::array<std::string_view, 2> interfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; | |
| const std::string& chassisId = param; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | |
| [asyncResp, chassisId, locationIndicatorActive, indicatorLed]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // Iterate over all retrieved ObjectPaths. | |
| for (const std::pair< | |
| std::string, | |
| std::vector<std::pair<std::string, std::vector<std::string>>>>& | |
| object : subtree) { | |
| const std::string& path = object.first; | |
| const std::vector<std::pair<std::string, std::vector<std::string>>>& | |
| connectionNames = object.second; | |
| sdbusplus::message::object_path objPath(path); | |
| if (objPath.filename() != chassisId) { | |
| continue; | |
| } | |
| if (connectionNames.empty()) { | |
| BMCWEB_LOG_ERROR << "Got 0 Connection names"; | |
| continue; | |
| } | |
| const std::vector<std::string>& interfaces3 = | |
| connectionNames[0].second; | |
| const std::array<const char*, 2> hasIndicatorLed = { | |
| "xyz.openbmc_project.Inventory.Item.Panel", | |
| "xyz.openbmc_project.Inventory.Item.Board.Motherboard"}; | |
| bool indicatorChassis = false; | |
| for (const char* interface : hasIndicatorLed) { | |
| if (std::find(interfaces3.begin(), interfaces3.end(), interface) != | |
| interfaces3.end()) { | |
| indicatorChassis = true; | |
| break; | |
| } | |
| } | |
| if (locationIndicatorActive) { | |
| if (indicatorChassis) { | |
| setLocationIndicatorActive(asyncResp, *locationIndicatorActive); | |
| } else { | |
| messages::propertyUnknown(asyncResp->res, | |
| "LocationIndicatorActive"); | |
| } | |
| } | |
| if (indicatorLed) { | |
| if (indicatorChassis) { | |
| setIndicatorLedState(asyncResp, *indicatorLed); | |
| } else { | |
| messages::propertyUnknown(asyncResp->res, "IndicatorLED"); | |
| } | |
| } | |
| return; | |
| } | |
| messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); | |
| }); | |
| } | |
| /** | |
| * Chassis override class for delivering Chassis Schema | |
| * Functions triggers appropriate requests on DBus | |
| */ | |
| inline void requestRoutesChassis(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/") | |
| .privileges(redfish::privileges::getChassis) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleChassisGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/") | |
| .privileges(redfish::privileges::patchChassis) | |
| .methods(boost::beast::http::verb::patch)( | |
| std::bind_front(handleChassisPatch, std::ref(app))); | |
| } | |
| void delayResetCN(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| int delayTimeSecs); | |
| /** | |
| * Clears the persisted host BIOS overrides (core count, BootNext, memory | |
| * capacity, boot source override) so the host(s) come back on defaults after a | |
| * power cycle. | |
| * | |
| * Scope: doChassisPowerCycle targets chassis_system0 (the whole tray) and | |
| * ignores the requested chassisId, so every system on the tray is reset and | |
| * clearing all of them is the matching behavior. If the power cycle is ever | |
| * made per-host, narrow this to ClearSystemBiosSettings(). | |
| * | |
| * Best effort: failures are logged and the power cycle still proceeds. | |
| */ | |
| inline void clearBiosSettingsForPowerCycle() { | |
| gbmc::settings::SettingsManager manager; | |
| if (absl::Status status = manager.ClearAllBiosSettings(); !status.ok()) { | |
| BMCWEB_LOG_ERROR << "Failed to clear BIOS settings: " << status; | |
| } | |
| } | |
| inline void doChassisPowerCycle( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| std::optional<int> delayTimeSecs) { | |
| constexpr std::array<std::string_view, 1> interfaces = { | |
| "xyz.openbmc_project.State.Chassis"}; | |
| // Use mapper to get subtree paths. | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/", 0, interfaces, requestContext, | |
| [asyncResp, delayTimeSecs]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& chassisList) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "[mapper] Bad D-Bus request error: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| const std::string processName = "xyz.openbmc_project.State.Chassis"; | |
| const std::string interfaceName = "xyz.openbmc_project.State.Chassis"; | |
| const std::string destProperty = "RequestedPowerTransition"; | |
| const std::string propertyValue = | |
| "xyz.openbmc_project.State.Chassis.Transition.PowerCycle"; | |
| std::string objectPath = "/xyz/openbmc_project/state/chassis_system0"; | |
| /* Look for system reset chassis path */ | |
| if ((std::find(chassisList.begin(), chassisList.end(), objectPath)) == | |
| chassisList.end()) { | |
| /* We prefer to reset the full chassis_system, but if it doesn't | |
| * exist on some platforms, fall back to a host-only power reset | |
| */ | |
| objectPath = "/xyz/openbmc_project/state/chassis0"; | |
| } | |
| if (delayTimeSecs.has_value()) { | |
| if (delayTimeSecs.value() > KMAX_RESET_DELAY_SEC) { | |
| BMCWEB_LOG_ERROR << "chassis reset delay out of range, request: " | |
| << delayTimeSecs.value() << " expect: 0-" | |
| << KMAX_RESET_DELAY_SEC << " seconds"; | |
| messages::propertyValueOutOfRange( | |
| asyncResp->res, "delay", std::to_string(KMAX_RESET_DELAY_SEC)); | |
| return; | |
| } | |
| // All inputs validated and the target chassis is resolved: the power | |
| // cycle is committed from here on. The reset itself happens later, | |
| // but these overrides are only consumed at host boot, so clearing | |
| // now is correct. | |
| clearBiosSettingsForPowerCycle(); | |
| delayResetCN(asyncResp, delayTimeSecs.value()); | |
| return; | |
| } | |
| // All inputs validated and the target chassis is resolved: the power | |
| // cycle is committed from here on. | |
| clearBiosSettingsForPowerCycle(); | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [asyncResp](const boost::system::error_code& ec2) { | |
| // Use "Set" method to set the property value. | |
| if (ec2) { | |
| BMCWEB_LOG_DEBUG << "[Set] Bad D-Bus request error: " | |
| << ec2; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| messages::success(asyncResp->res); | |
| }), | |
| processName, objectPath, "org.freedesktop.DBus.Properties", | |
| "Set", interfaceName, destProperty, | |
| dbus::utility::DbusVariantType{propertyValue}); | |
| }); | |
| } | |
| /** | |
| * validateChassisResetAction checks the Chassis type to ensure the Chassis | |
| * supports Chassis Reset. | |
| * | |
| * Checks the Chassis type for `RackMount` or `StandAlone` for Chassis Reset. | |
| * Otherwise, reset is not supported. | |
| */ | |
| inline void validateChassisResetAction( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, | |
| std::function<void(const boost::system::error_code ec, | |
| const std::variant<std::string>&)>&& callback) { | |
| std::array<std::string_view, 2> interfaces{ | |
| "xyz.openbmc_project.Inventory.Item.Board", | |
| "xyz.openbmc_project.Inventory.Item.Chassis"}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | |
| [asyncResp, chassisId, callback(std::move(callback))]( | |
| const boost::system::error_code ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) mutable { | |
| if (ec) { | |
| callback(ec, std::variant<std::string>{""}); | |
| return; | |
| } | |
| for (const std::pair< | |
| std::string, | |
| std::vector<std::pair<std::string, std::vector<std::string>>>>& | |
| object : subtree) { | |
| sdbusplus::message::object_path path(object.first); | |
| if (path.filename() != chassisId) { | |
| continue; | |
| } | |
| const std::vector<std::pair<std::string, std::vector<std::string>>>& | |
| connectionNames = object.second; | |
| if (connectionNames.empty()) { | |
| BMCWEB_LOG_ERROR << "Got 0 Connection names"; | |
| continue; | |
| } | |
| getChassisType(asyncResp, connectionNames[0].first, path.str, | |
| std::move(callback)); | |
| return; | |
| } | |
| messages::resourceNotFound(asyncResp->res, "#Chassis.v1_14_0.Chassis", | |
| chassisId); | |
| }); | |
| } | |
| inline void validateChassisResetActionHandler( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| std::optional<std::string> resetType, | |
| const std::optional<int> delayTimeSecs, const boost::system::error_code ec, | |
| const std::variant<std::string>& property) { | |
| if (ec) { | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| std::optional<std::string> chassisType = getChassisTypeProperty(property); | |
| if (chassisType && *chassisType != "RackMount" && | |
| *chassisType != "StandAlone") { | |
| messages::actionNotSupported(asyncResp->res, | |
| "Only RackMount or StandAlone support Chassis " | |
| "Reset. Got Chassis type of " + | |
| *chassisType); | |
| return; | |
| } | |
| if (resetType && *resetType != "PowerCycle") { | |
| BMCWEB_LOG_DEBUG << "Invalid property value for " | |
| "ResetType: " | |
| << *resetType; | |
| messages::actionParameterNotSupported(asyncResp->res, *resetType, | |
| "ResetType"); | |
| return; | |
| } | |
| doChassisPowerCycle(asyncResp, delayTimeSecs); | |
| } | |
| inline void handleChassisResetActionInfoPost( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Post Chassis Reset."; | |
| std::optional<std::string> resetType; | |
| std::optional<int> delayTimeSecs; | |
| if (!json_util::readJsonAction(req, asyncResp->res, "ResetType", resetType, | |
| "Delay", delayTimeSecs)) { | |
| return; | |
| } | |
| validateChassisResetAction( | |
| asyncResp, chassisId, | |
| std::bind_front(validateChassisResetActionHandler, asyncResp, resetType, | |
| delayTimeSecs)); | |
| } | |
| /** | |
| * ChassisResetAction class supports the POST method for the Reset | |
| * action. | |
| * Function handles POST method request. | |
| * Analyzes POST body before sending Reset request data to D-Bus. | |
| */ | |
| inline void requestRoutesChassisResetAction(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Actions/Chassis.Reset/") | |
| .privileges(redfish::privileges::postChassis) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handleChassisResetActionInfoPost, std::ref(app))); | |
| } | |
| inline void validateChassisResetActionInfoHandler( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId, const boost::system::error_code ec, | |
| const std::variant<std::string>& property) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_1_2.ActionInfo"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "ResetActionInfo"); | |
| asyncResp->res.jsonValue["Name"] = "Reset Action Info"; | |
| asyncResp->res.jsonValue["Id"] = "ResetActionInfo"; | |
| nlohmann::json::array_t parameters; | |
| nlohmann::json::object_t parameter; | |
| parameter["Name"] = "ResetType"; | |
| parameter["Required"] = true; | |
| parameter["DataType"] = "String"; | |
| nlohmann::json::array_t allowed; | |
| allowed.emplace_back("PowerCycle"); | |
| nlohmann::json::object_t delaySecsParameter; | |
| // argument names are consistent with system delay | |
| delaySecsParameter["Name"] = "Delay"; | |
| delaySecsParameter["Required"] = false; | |
| delaySecsParameter["DataType"] = "Number"; | |
| // xyz.openbmc_project.Inventory.Item.Chassis not found on the Chassis | |
| // Default to PowerCycle | |
| if (ec) { | |
| parameter["AllowableValues"] = std::move(allowed); | |
| parameters.emplace_back(std::move(parameter)); | |
| parameters.emplace_back(std::move(delaySecsParameter)); | |
| asyncResp->res.jsonValue["Parameters"] = std::move(parameters); | |
| return; | |
| } | |
| std::optional<std::string> chassisType = getChassisTypeProperty(property); | |
| if (chassisType && *chassisType != "RackMount" && | |
| *chassisType != "StandAlone") { | |
| BMCWEB_LOG_DEBUG << "Only RackMount support Chassis Reset. Got " | |
| "Chassis type of " | |
| << *chassisType; | |
| // Remove PowerCycle as allowed value. | |
| allowed.pop_back(); | |
| } | |
| parameter["AllowableValues"] = std::move(allowed); | |
| parameters.emplace_back(std::move(parameter)); | |
| parameters.emplace_back(std::move(delaySecsParameter)); | |
| asyncResp->res.jsonValue["Parameters"] = std::move(parameters); | |
| } | |
| inline void handleChassisResetActionInfoGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| validateChassisResetAction( | |
| asyncResp, chassisId, | |
| std::bind_front(validateChassisResetActionInfoHandler, asyncResp, | |
| chassisId)); | |
| } | |
| /** | |
| * ChassisResetActionInfo derived class for delivering Chassis | |
| * ResetType AllowableValues using ResetInfo schema. | |
| */ | |
| inline void requestRoutesChassisResetActionInfo(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ResetActionInfo/") | |
| .privileges(redfish::privileges::getActionInfo) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleChassisResetActionInfoGet, std::ref(app))); | |
| } | |
| inline void getChassisAssembly( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& chassisId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| chassis_utils::getValidChassisPath( | |
| asyncResp, chassisId, | |
| [asyncResp, chassisId](const std::optional<std::string>& path) { | |
| if (!path) { | |
| messages::resourceNotFound(asyncResp->res, "#Chassis.v1_16_0.Chassis", | |
| chassisId); | |
| return; | |
| } | |
| boost::urls::url assemblyId = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId, "Assembly"); | |
| getAssembly(asyncResp, assemblyId, *path); | |
| }); | |
| } | |
| inline void requestRoutesChassisAssembly(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Assembly/") | |
| .privileges(redfish::privileges::getAssembly) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(getChassisAssembly, std::ref(app))); | |
| } | |
| } // namespace redfish | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_CHASSIS_H_ |