/* // Copyright (c) 2019 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. */ #pragma once #include "health.hpp" #include "openbmc_dbus_rest.hpp" #include namespace redfish { class StorageCollection : public Node { public: StorageCollection(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/Storage/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { res.jsonValue["@odata.type"] = "#StorageCollection.StorageCollection"; res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/Storage"; res.jsonValue["Name"] = "Storage Collection"; res.jsonValue["Members"] = { {{"@odata.id", "/redfish/v1/Systems/system/Storage/1"}}}; res.jsonValue["Members@odata.count"] = 1; res.end(); } }; class Storage : public Node { public: Storage(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/Storage/1/") { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage"; res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/Storage/1"; res.jsonValue["Name"] = "Storage"; res.jsonValue["Id"] = "1"; res.jsonValue["Status"]["State"] = "Enabled"; auto asyncResp = std::make_shared(res); auto health = std::make_shared(asyncResp); health->populate(); crow::connections::systemBus->async_method_call( [asyncResp, health](const boost::system::error_code ec, const std::vector &storageList) { nlohmann::json &storageArray = asyncResp->res.jsonValue["Drives"]; storageArray = nlohmann::json::array(); auto &count = asyncResp->res.jsonValue["Drives@odata.count"]; count = 0; if (ec) { BMCWEB_LOG_ERROR << "Drive mapper call error"; messages::internalError(asyncResp->res); return; } health->inventory.insert(health->inventory.end(), storageList.begin(), storageList.end()); for (const std::string &objpath : storageList) { std::size_t lastPos = objpath.rfind("/"); if (lastPos == std::string::npos || (objpath.size() <= lastPos + 1)) { BMCWEB_LOG_ERROR << "Failed to find '/' in " << objpath; continue; } storageArray.push_back( {{"@odata.id", "/redfish/v1/Systems/system/Storage/1/Drives/" + objpath.substr(lastPos + 1)}}); } count = storageArray.size(); }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/xyz/openbmc_project/inventory", int32_t(0), std::array{ "xyz.openbmc_project.Inventory.Item.Drive"}); crow::connections::systemBus->async_method_call( [asyncResp, health](const boost::system::error_code ec, const crow::openbmc_mapper::GetSubTreeType &subtree) { if (ec || !subtree.size()) { // doesn't have to be there return; } nlohmann::json &root = asyncResp->res.jsonValue["StorageControllers"]; root = nlohmann::json::array(); for (const auto &[path, interfaceDict] : subtree) { std::size_t lastPos = path.rfind("/"); if (lastPos == std::string::npos || (path.size() <= lastPos + 1)) { BMCWEB_LOG_ERROR << "Failed to find '/' in " << path; return; } if (interfaceDict.size() != 1) { BMCWEB_LOG_ERROR << "Connection size " << interfaceDict.size() << ", greater than 1"; messages::internalError(asyncResp->res); return; } const std::string &connectionName = interfaceDict.front().first; size_t index = root.size(); nlohmann::json &storageController = root.emplace_back(nlohmann::json::object()); std::string id = path.substr(lastPos + 1); storageController["@odata.type"] = "#Storage.v1_7_0.StorageController"; storageController["@odata.id"] = "/redfish/v1/Systems/system/Storage/1" "#/StorageControllers/" + std::to_string(index); storageController["Name"] = id; storageController["MemberId"] = id; storageController["Status"]["State"] = "Enabled"; crow::connections::systemBus->async_method_call( [asyncResp, index](const boost::system::error_code ec, const std::variant present) { // this interface isn't necessary, only check it if // we get a good return if (ec) { return; } const bool *enabled = std::get_if(&present); if (enabled == nullptr) { BMCWEB_LOG_DEBUG << "Illegal property present"; messages::internalError(asyncResp->res); return; } if (!(*enabled)) { asyncResp->res .jsonValue["StorageControllers"][index] ["Status"]["State"] = "Disabled"; } }, connectionName, path, "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.Inventory.Item", "Present"); crow::connections::systemBus->async_method_call( [asyncResp, index](const boost::system::error_code ec, const std::vector>> &propertiesList) { if (ec) { // this interface isn't necessary return; } for (const std::pair< std::string, std::variant> &property : propertiesList) { // Store DBus properties that are also // Redfish properties with same name and a // string value const std::string &propertyName = property.first; nlohmann::json &object = asyncResp->res .jsonValue["StorageControllers"][index]; if ((propertyName == "PartNumber") || (propertyName == "SerialNumber") || (propertyName == "Manufacturer") || (propertyName == "Model")) { const std::string *value = std::get_if( &property.second); if (value == nullptr) { // illegal property messages::internalError(asyncResp->res); continue; } object[propertyName] = *value; } } }, connectionName, path, "org.freedesktop.DBus.Properties", "GetAll", "xyz.openbmc_project.Inventory.Decorator.Asset"); } // this is done after we know the json array will no longer be // resized, as json::array uses vector underneath and we need // references to its members that won't change size_t count = 0; for (const auto &[path, interfaceDict] : subtree) { auto subHealth = std::make_shared( asyncResp, root[count]["Status"]); subHealth->inventory.emplace_back(path); health->inventory.emplace_back(path); health->children.emplace_back(subHealth); count++; } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/xyz/openbmc_project/inventory", int32_t(0), std::array{ "xyz.openbmc_project.Inventory.Item.StorageController"}); } }; class Drive : public Node { public: Drive(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/Storage/1/Drives//", std::string()) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; } private: void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { auto asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } const std::string &driveId = params[0]; crow::connections::systemBus->async_method_call( [asyncResp, driveId](const boost::system::error_code ec, const crow::openbmc_mapper::GetSubTreeType &subtree) { if (ec) { BMCWEB_LOG_ERROR << "Drive mapper call error"; messages::internalError(asyncResp->res); return; } auto object = std::find_if( subtree.begin(), subtree.end(), [&driveId](auto &object) { const std::string &path = object.first; return boost::ends_with(path, "/" + driveId); }); if (object == subtree.end()) { messages::resourceNotFound(asyncResp->res, "Drive", driveId); return; } const std::string &path = object->first; const std::vector< std::pair>> &connectionNames = object->second; asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/Storage/1/Drives/" + driveId; asyncResp->res.jsonValue["Name"] = driveId; asyncResp->res.jsonValue["Id"] = driveId; if (connectionNames.size() != 1) { BMCWEB_LOG_ERROR << "Connection size " << connectionNames.size() << ", greater than 1"; messages::internalError(asyncResp->res); return; } getMainChassisId( asyncResp, [](const std::string &chassisId, std::shared_ptr aRsp) { aRsp->res.jsonValue["Links"]["Chassis"] = { {"@odata.id", "/redfish/v1/Chassis/" + chassisId}}; }); const std::string &connectionName = connectionNames[0].first; crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, const std::vector>> &propertiesList) { if (ec) { // this interface isn't necessary return; } for (const std::pair> &property : propertiesList) { // Store DBus properties that are also // Redfish properties with same name and a // string value const std::string &propertyName = property.first; if ((propertyName == "PartNumber") || (propertyName == "SerialNumber") || (propertyName == "Manufacturer") || (propertyName == "Model")) { const std::string *value = std::get_if(&property.second); if (value == nullptr) { // illegal property messages::internalError(asyncResp->res); continue; } asyncResp->res.jsonValue[propertyName] = *value; } } }, connectionName, path, "org.freedesktop.DBus.Properties", "GetAll", "xyz.openbmc_project.Inventory.Decorator.Asset"); // default it to Enabled asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; auto health = std::make_shared(asyncResp); health->inventory.emplace_back(path); health->populate(); crow::connections::systemBus->async_method_call( [asyncResp, path](const boost::system::error_code ec, const std::variant present) { // this interface isn't necessary, only check it if we // get a good return if (ec) { return; } const bool *enabled = std::get_if(&present); if (enabled == nullptr) { BMCWEB_LOG_DEBUG << "Illegal property present"; messages::internalError(asyncResp->res); return; } if (!(*enabled)) { asyncResp->res.jsonValue["Status"]["State"] = "Disabled"; } }, connectionName, path, "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.Inventory.Item", "Present"); crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, const std::variant rebuilding) { // this interface isn't necessary, only check it if we // get a good return if (ec) { return; } const bool *updating = std::get_if(&rebuilding); if (updating == nullptr) { BMCWEB_LOG_DEBUG << "Illegal property present"; messages::internalError(asyncResp->res); return; } // updating and disabled in the backend shouldn't be // able to be set at the same time, so we don't need to // check for the race condition of these two calls if ((*updating)) { asyncResp->res.jsonValue["Status"]["State"] = "Updating"; } }, connectionName, path, "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.State.Drive", "Rebuilding"); }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/xyz/openbmc_project/inventory", int32_t(0), std::array{ "xyz.openbmc_project.Inventory.Item.Drive"}); } }; } // namespace redfish