From 461da7ec950704a1f5bcc7f6527ed8ca119cfaf9 Mon Sep 17 00:00:00 2001 From: Vikram Bodireddy Date: Tue, 24 Mar 2020 16:05:32 +0530 Subject: [PATCH] Firmware update support for StandBySpare Firmware update support for StandBySpare. This will have support for adding 'HttpPushUriTargets' and 'HttpPushUriTargetsBusy' attributes. These attributes enables 'HttpPushUri' to distinguish between the firmware update targets. Tested: - GET on "/redfish/v1/UpdateService", got below response ......... "HttpPushUriTargets": [], "HttpPushUriTargetsBusy": false ........ - PATCH on "/redfish/v1/UpdateService" and works fine. { "HttpPushUriTargets": ["bmc_recovery"], "HttpPushUriTargetsBusy": true } - Did Firmware update and verified end to end functionality for both bmc active and backup images. - Successfully ran redfish validater with no new errors. Signed-off-by: Vikram Bodireddy --- redfish-core/lib/update_service.hpp | 274 +++++++++++++++++++++++++++++++----- 1 file changed, 241 insertions(+), 33 deletions(-) diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp index e9793eb..a913bac 100644 --- a/redfish-core/lib/update_service.hpp +++ b/redfish-core/lib/update_service.hpp @@ -30,6 +30,17 @@ static std::unique_ptr fwUpdateMatcher; static bool fwUpdateInProgress = false; // Timer for software available static std::unique_ptr fwAvailableTimer; +static constexpr const char *versionIntf = + "xyz.openbmc_project.Software.Version"; +static constexpr const char *activationIntf = + "xyz.openbmc_project.Software.Activation"; +static constexpr const char *reqActivationPropName = "RequestedActivation"; +static constexpr const char *reqActivationsActive = + "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"; +static constexpr const char *reqActivationsStandBySpare = + "xyz.openbmc_project.Software.Activation.RequestedActivations.StandbySpare"; +static constexpr const char *activationsStandBySpare = + "xyz.openbmc_project.Software.Activation.Activations.StandbySpare"; static void cleanUp() { @@ -37,27 +48,119 @@ static void cleanUp() fwUpdateMatcher = nullptr; } static void activateImage(const std::string &objPath, - const std::string &service) + const std::string &service, + const std::vector &imgUriTargets) { BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service; + // If targets is empty, it will apply to the active. + if (imgUriTargets.size() == 0) + { + crow::connections::systemBus->async_method_call( + [](const boost::system::error_code error_code) { + if (error_code) + { + BMCWEB_LOG_DEBUG + << "RequestedActivation failed: error_code = " + << error_code; + BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); + } + }, + service, objPath, "org.freedesktop.DBus.Properties", "Set", + activationIntf, reqActivationPropName, + std::variant(reqActivationsActive)); + return; + } + + // TODO: Now we support only one target becuase software-manager + // code support one activation per object. It will be enhanced + // to multiple targets for single image in future. For now, + // consider first target alone. crow::connections::systemBus->async_method_call( - [](const boost::system::error_code error_code) { - if (error_code) + [objPath, service, imgTarget{imgUriTargets[0]}]( + const boost::system::error_code ec, + const crow::openbmc_mapper::GetSubTreeType &subtree) { + if (ec || !subtree.size()) { - BMCWEB_LOG_DEBUG << "error_code = " << error_code; - BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); + return; + } + + for (const auto &[invObjPath, invDict] : subtree) + { + std::size_t idPos = invObjPath.rfind("/"); + if ((idPos == std::string::npos) || + ((idPos + 1) >= invObjPath.size())) + { + BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; + return; + } + std::string swId = invObjPath.substr(idPos + 1); + + if (swId != imgTarget) + { + continue; + } + + if (invDict.size() < 1) + { + continue; + } + BMCWEB_LOG_DEBUG << "Image target matched with object " + << invObjPath; + crow::connections::systemBus->async_method_call( + [objPath, + service](const boost::system::error_code error_code, + const std::variant value) { + if (error_code) + { + BMCWEB_LOG_DEBUG + << "Error in querying activation value"; + // not all fwtypes are updateable, + // this is ok + return; + } + std::string activationValue = + std::get(value); + BMCWEB_LOG_DEBUG << "Activation Value: " + << activationValue; + std::string reqActivation = reqActivationsActive; + if (activationValue == activationsStandBySpare) + { + reqActivation = reqActivationsStandBySpare; + } + BMCWEB_LOG_DEBUG + << "Setting RequestedActivation value as " + << reqActivation << " for " << service << " " + << objPath; + crow::connections::systemBus->async_method_call( + [](const boost::system::error_code error_code) { + if (error_code) + { + BMCWEB_LOG_DEBUG + << "RequestedActivation failed: ec = " + << error_code; + } + return; + }, + service, objPath, "org.freedesktop.DBus.Properties", + "Set", activationIntf, reqActivationPropName, + std::variant(reqActivation)); + }, + invDict[0].first, + "/xyz/openbmc_project/software/" + imgTarget, + "org.freedesktop.DBus.Properties", "Get", activationIntf, + "Activation"); } }, - service, objPath, "org.freedesktop.DBus.Properties", "Set", - "xyz.openbmc_project.Software.Activation", "RequestedActivation", - std::variant( - "xyz.openbmc_project.Software.Activation.RequestedActivations." - "Active")); + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", + static_cast(0), std::array{versionIntf}); } // Note that asyncResp can be either a valid pointer or nullptr. If nullptr // then no asyncResp updates will occur static void softwareInterfaceAdded(std::shared_ptr asyncResp, + const std::vector imgUriTargets, sdbusplus::message::message &m, const crow::Request &req) { @@ -70,25 +173,27 @@ static void softwareInterfaceAdded(std::shared_ptr asyncResp, m.read(objPath, interfacesProperties); - BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; + BMCWEB_LOG_DEBUG << "Software Interface Added. obj path = " << objPath.str; for (auto &interface : interfacesProperties) { BMCWEB_LOG_DEBUG << "interface = " << interface.first; - if (interface.first == "xyz.openbmc_project.Software.Activation") + if (interface.first == activationIntf) { // Found our interface, disable callbacks fwUpdateMatcher = nullptr; // Retrieve service and activate crow::connections::systemBus->async_method_call( - [objPath, asyncResp, + [objPath, asyncResp, imgTargets{imgUriTargets}, req](const boost::system::error_code error_code, const std::vector>> &objInfo) { if (error_code) { - BMCWEB_LOG_DEBUG << "error_code = " << error_code; + BMCWEB_LOG_DEBUG + << "GetSoftwareObject path failed: error_code = " + << error_code; BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); if (asyncResp) @@ -115,7 +220,7 @@ static void softwareInterfaceAdded(std::shared_ptr asyncResp, // is added fwAvailableTimer = nullptr; - activateImage(objPath.str, objInfo[0].first); + activateImage(objPath.str, objInfo[0].first, imgTargets); if (asyncResp) { std::shared_ptr task = @@ -196,17 +301,16 @@ static void softwareInterfaceAdded(std::shared_ptr asyncResp, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, - std::array{ - "xyz.openbmc_project.Software.Activation"}); + std::array{activationIntf}); } } } // Note that asyncResp can be either a valid pointer or nullptr. If nullptr // then no asyncResp updates will occur -static void monitorForSoftwareAvailable(std::shared_ptr asyncResp, - const crow::Request &req, - int timeoutTimeSeconds = 5) +static void monitorForSoftwareAvailable( + std::shared_ptr asyncResp, const crow::Request &req, + const std::vector &imgUriTargets, int timeoutTimeSeconds = 5) { // Only allow one FW update at a time if (fwUpdateInProgress != false) @@ -246,9 +350,10 @@ static void monitorForSoftwareAvailable(std::shared_ptr asyncResp, } }); - auto callback = [asyncResp, req](sdbusplus::message::message &m) { + auto callback = [asyncResp, imgTargets{imgUriTargets}, + req](sdbusplus::message::message &m) { BMCWEB_LOG_DEBUG << "Match fired"; - softwareInterfaceAdded(asyncResp, m, req); + softwareInterfaceAdded(asyncResp, imgTargets, m, req); }; fwUpdateInProgress = true; @@ -358,9 +463,12 @@ class UpdateServiceActionsSimpleUpdate : public Node std::string fwFile = imageURI.substr(separator + 1); BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile; + // We will pass empty targets and its handled in activation. + std::vector httpUriTargets; + // Setup callback for when new software detected // Give TFTP 2 minutes to complete - monitorForSoftwareAvailable(nullptr, req, 120); + monitorForSoftwareAvailable(nullptr, req, httpUriTargets, 120); // TFTP can take up to 2 minutes depending on image size and // connection speed. Return to caller as soon as the TFTP operation @@ -394,7 +502,8 @@ class UpdateServiceActionsSimpleUpdate : public Node class UpdateService : public Node { public: - UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/") + UpdateService(CrowApp &app) : + Node(app, "/redfish/v1/UpdateService/"), httpPushUriTargetBusy(false) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, @@ -406,6 +515,9 @@ class UpdateService : public Node } private: + std::vector httpPushUriTargets; + bool httpPushUriTargetBusy; + void doGet(crow::Response &res, const crow::Request &req, const std::vector ¶ms) override { @@ -416,6 +528,8 @@ class UpdateService : public Node res.jsonValue["Description"] = "Service for Software Update"; res.jsonValue["Name"] = "Update Service"; res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; + res.jsonValue["HttpPushUriTargets"] = httpPushUriTargets; + res.jsonValue["HttpPushUriTargetsBusy"] = httpPushUriTargetBusy; // UpdateService cannot be disabled res.jsonValue["ServiceEnabled"] = true; res.jsonValue["FirmwareInventory"] = { @@ -475,9 +589,14 @@ class UpdateService : public Node std::shared_ptr asyncResp = std::make_shared(res); std::optional pushUriOptions; - if (!json_util::readJson(req, res, "HttpPushUriOptions", - pushUriOptions)) + std::optional> imgTargets; + std::optional imgTargetBusy; + + if (!json_util::readJson(req, res, "HttpPushUriOptions", pushUriOptions, + "HttpPushUriTargets", imgTargets, + "HttpPushUriTargetsBusy", imgTargetBusy)) { + BMCWEB_LOG_DEBUG << "UpdateService doPatch: Invalid request body"; return; } @@ -545,6 +664,98 @@ class UpdateService : public Node } } } + + if (imgTargetBusy) + { + if ((httpPushUriTargetBusy) && (*imgTargetBusy)) + { + BMCWEB_LOG_DEBUG + << "Other client has reserved the HttpPushUriTargets " + "property for firmware updates."; + messages::resourceInUse(asyncResp->res); + return; + } + + if (imgTargets) + { + if (!(*imgTargetBusy)) + { + BMCWEB_LOG_DEBUG + << "UpdateService doPatch: httpPushUriTargetBusy " + "should be " + "true before setting httpPushUriTargets"; + messages::invalidObject(asyncResp->res, + "HttpPushUriTargetsBusy"); + return; + } + if ((*imgTargets).size() != 0) + { + // TODO: Now we support max one target becuase + // software-manager code support one activation per object. + // It will be enhanced to multiple targets for single image + // in future. For now, consider first target alone. + if ((*imgTargets).size() != 1) + { + messages::invalidObject(asyncResp->res, + "HttpPushUriTargets"); + return; + } + crow::connections::systemBus->async_method_call( + [this, asyncResp, uriTargets{*imgTargets}, + targetBusy{*imgTargetBusy}]( + const boost::system::error_code ec, + const std::vector swInvPaths) { + if (ec) + { + return; + } + + bool swInvObjFound = false; + for (const std::string &path : swInvPaths) + { + std::size_t idPos = path.rfind("/"); + if ((idPos == std::string::npos) || + ((idPos + 1) >= path.size())) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_DEBUG + << "Can't parse firmware ID!!"; + return; + } + std::string swId = path.substr(idPos + 1); + + if (swId == uriTargets[0]) + { + swInvObjFound = true; + break; + } + } + if (!swInvObjFound) + { + messages::invalidObject(asyncResp->res, + "HttpPushUriTargets"); + return; + } + this->httpPushUriTargetBusy = targetBusy; + this->httpPushUriTargets = uriTargets; + }, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", + "/", static_cast(0), + std::array{versionIntf}); + } + else + { + httpPushUriTargetBusy = *imgTargetBusy; + httpPushUriTargets = *imgTargets; + } + } + else + { + httpPushUriTargetBusy = *imgTargetBusy; + } + } } void doPost(crow::Response &res, const crow::Request &req, @@ -555,7 +766,7 @@ class UpdateService : public Node std::shared_ptr asyncResp = std::make_shared(res); // Setup callback for when new software detected - monitorForSoftwareAvailable(asyncResp, req); + monitorForSoftwareAvailable(asyncResp, req, httpPushUriTargets); std::string filepath( "/tmp/images/" + @@ -641,8 +852,7 @@ class SoftwareInventoryCollection : public Node "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/xyz/openbmc_project/software", static_cast(0), - std::array{ - "xyz.openbmc_project.Software.Version"}); + std::array{versionIntf}); } }; @@ -825,7 +1035,7 @@ class SoftwareInventory : public Node }, obj.second[0].first, obj.first, "org.freedesktop.DBus.Properties", "GetAll", - "xyz.openbmc_project.Software.Version"); + versionIntf); } if (!found) { @@ -846,9 +1056,7 @@ class SoftwareInventory : public Node "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", - static_cast(0), - std::array{ - "xyz.openbmc_project.Software.Version"}); + static_cast(0), std::array{versionIntf}); } }; -- 2.7.4