From 10cb7cb14974725a29b3ead4c543ca5e58234c07 Mon Sep 17 00:00:00 2001 From: Vikram Bodireddy Date: Wed, 18 Nov 2020 17:14:41 +0530 Subject: [PATCH] Firmware update configuration changes This commit will provide user to PATCH the below firmware update attributes before uploding the firmware image. 1. This will have PATCH support for 'HttpPushUriTargets' and 'HttpPushUriTargetsBusy' attributes. These attributes enables 'HttpPushUri' to distinguish between the firmware update targets. 2. ApplyOptions are used to specify firmware update specific options such as ClearConfig which is used while activating the updated firmware. This setting is maintained in a local static variable when set using PATCH method. Its used in activate image as input parameter. This attribute is added as Oem as the default UpdateService interface doesn't specify any relevant or appropriate attribute for this. 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. - Tested setting ClearConfig to true or false using PATCH method. - Successfully ran redfish validater with no new errors. Signed-off-by: Vikram Bodireddy %% original patch: 0001-Firmware-update-configuration-changes.patch Change-Id: I44e1743fd76aa37c7b8affa49a3e05f808187037 Signed-off-by: Helen Huang --- redfish-core/lib/update_service.hpp | 339 ++++++++++++++++-- static/redfish/v1/$metadata/index.xml | 3 + .../JsonSchemas/OemUpdateService/index.json | 69 ++++ .../redfish/v1/schema/OemUpdateService_v1.xml | 40 +++ 4 files changed, 421 insertions(+), 30 deletions(-) create mode 100644 static/redfish/v1/JsonSchemas/OemUpdateService/index.json create mode 100644 static/redfish/v1/schema/OemUpdateService_v1.xml diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp index 6d44171..8eda265 100644 --- a/redfish-core/lib/update_service.hpp +++ b/redfish-core/lib/update_service.hpp @@ -32,6 +32,17 @@ static std::unique_ptr fwUpdateErrorMatcher; 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() { @@ -40,27 +51,119 @@ static void cleanUp() fwUpdateErrorMatcher = 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 errorCode) { - if (errorCode) + [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 = " << errorCode; - BMCWEB_LOG_DEBUG << "error msg = " << errorCode.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(const std::shared_ptr& asyncResp, + const std::vector imgUriTargets, sdbusplus::message::message& m, const crow::Request& req) { @@ -73,22 +176,24 @@ static void softwareInterfaceAdded(const 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) { // Retrieve service and activate crow::connections::systemBus->async_method_call( - [objPath, asyncResp, + [objPath, asyncResp, imgTargets{imgUriTargets}, req](const boost::system::error_code errorCode, const std::vector>>& objInfo) { if (errorCode) { - BMCWEB_LOG_DEBUG << "error_code = " << errorCode; + BMCWEB_LOG_DEBUG + << "GetSoftwareObject path failed: error_code = " + << errorCode; BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message(); if (asyncResp) @@ -115,7 +220,7 @@ static void softwareInterfaceAdded(const 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 = @@ -247,8 +352,7 @@ static void softwareInterfaceAdded(const 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}); } } } @@ -257,7 +361,8 @@ static void softwareInterfaceAdded(const std::shared_ptr& asyncResp, // then no asyncResp updates will occur static void monitorForSoftwareAvailable( const std::shared_ptr& asyncResp, const crow::Request& req, - const std::string& url, int timeoutTimeSeconds = 10) + const std::string& url, const std::vector& imgUriTargets, + int timeoutTimeSeconds = 10) { // Only allow one FW update at a time if (fwUpdateInProgress != false) @@ -297,9 +402,10 @@ static void monitorForSoftwareAvailable( } }); - 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; @@ -475,12 +581,15 @@ 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 10 minutes to complete monitorForSoftwareAvailable( asyncResp, req, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", - 600); + httpUriTargets, 600); // TFTP can take up to 10 minutes depending on image size and // connection speed. Return to caller as soon as the TFTP operation @@ -514,7 +623,8 @@ class UpdateServiceActionsSimpleUpdate : public Node class UpdateService : public Node { public: - UpdateService(App& app) : Node(app, "/redfish/v1/UpdateService/") + UpdateService(App& app) : + Node(app, "/redfish/v1/UpdateService/"), httpPushUriTargetBusy(false) { entityPrivileges = { {boost::beast::http::verb::get, {{"Login"}}}, @@ -526,6 +636,8 @@ class UpdateService : public Node } private: + std::vector httpPushUriTargets; + bool httpPushUriTargetBusy; void doGet(crow::Response& res, const crow::Request&, const std::vector&) override { @@ -536,6 +648,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"] = { @@ -585,6 +699,31 @@ class UpdateService : public Node "/xyz/openbmc_project/software/apply_time", "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime"); + + // Get the ApplyOptions value + crow::connections::systemBus->async_method_call( + [aResp](const boost::system::error_code ec, + const std::variant applyOption) { + if (ec) + { + BMCWEB_LOG_DEBUG << "DBUS response error " << ec; + messages::internalError(aResp->res); + return; + } + + const bool* b = std::get_if(&applyOption); + + if (b) + { + aResp->res.jsonValue["Oem"]["ApplyOptions"]["@odata.type"] = + "#OemUpdateService.ApplyOptions"; + aResp->res.jsonValue["Oem"]["ApplyOptions"]["ClearConfig"] = + *b; + } + }, + "xyz.openbmc_project.Software.BMC.Updater", + "/xyz/openbmc_project/software", "org.freedesktop.DBus.Properties", + "Get", "xyz.openbmc_project.Software.ApplyOptions", "ClearConfig"); } void doPatch(crow::Response& res, const crow::Request& req, @@ -595,12 +734,61 @@ 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; + std::optional oemProps; + + if (!json_util::readJson(req, res, "HttpPushUriOptions", pushUriOptions, + "HttpPushUriTargets", imgTargets, + "HttpPushUriTargetsBusy", imgTargetBusy, "Oem", + oemProps)) { + BMCWEB_LOG_DEBUG << "UpdateService doPatch: Invalid request body"; return; } + if (oemProps) + { + std::optional applyOptions; + + if (!json_util::readJson(*oemProps, res, "ApplyOptions", + applyOptions)) + { + return; + } + + if (applyOptions) + { + std::optional clearConfig; + if (!json_util::readJson(*applyOptions, res, "ClearConfig", + clearConfig)) + { + return; + } + + if (clearConfig) + { + // Set the requested image apply time value + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec) { + if (ec) + { + BMCWEB_LOG_ERROR << "D-Bus responses error: " + << ec; + messages::internalError(asyncResp->res); + return; + } + messages::success(asyncResp->res); + }, + "xyz.openbmc_project.Software.BMC.Updater", + "/xyz/openbmc_project/software", + "org.freedesktop.DBus.Properties", "Set", + "xyz.openbmc_project.Software.ApplyOptions", + "ClearConfig", std::variant{*clearConfig}); + } + } + } + if (pushUriOptions) { std::optional pushUriApplyTime; @@ -665,6 +853,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, @@ -675,8 +955,8 @@ class UpdateService : public Node std::shared_ptr asyncResp = std::make_shared(res); // Setup callback for when new software detected - monitorForSoftwareAvailable(asyncResp, req, - "/redfish/v1/UpdateService"); + monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService", + httpPushUriTargets); std::string filepath( "/tmp/images/" + @@ -761,7 +1041,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}); } }; @@ -943,7 +1223,7 @@ class SoftwareInventory : public Node }, obj.second[0].first, obj.first, "org.freedesktop.DBus.Properties", "GetAll", - "xyz.openbmc_project.Software.Version"); + versionIntf); } if (!found) { @@ -964,8 +1244,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}); } }; diff --git a/static/redfish/v1/$metadata/index.xml b/static/redfish/v1/$metadata/index.xml index 514f3dd..c068d4f 100644 --- a/static/redfish/v1/$metadata/index.xml +++ b/static/redfish/v1/$metadata/index.xml @@ -2142,6 +2142,9 @@ + + + diff --git a/static/redfish/v1/JsonSchemas/OemUpdateService/index.json b/static/redfish/v1/JsonSchemas/OemUpdateService/index.json new file mode 100644 index 0000000..74e39cd --- /dev/null +++ b/static/redfish/v1/JsonSchemas/OemUpdateService/index.json @@ -0,0 +1,69 @@ +{ + "$id": "http://redfish.dmtf.org/schemas/v1/OemUpdateService.json", + "$schema": "http://redfish.dmtf.org/schemas/v1/redfish-schema-v1.json", + "copyright": "Copyright 2014-2019 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright", + "definitions": { + "ApplyOptions": { + "additionalProperties": false, + "description": "An indication by boolean value whether to update firmware configuration along with firmware image update.", + "patternProperties": { + "^([a-zA-Z_][a-zA-Z0-9_]*)?@(odata|Redfish|Message)\\.[a-zA-Z_][a-zA-Z0-9_]*$": { + "description": "This property shall specify a valid odata or Redfish property.", + "type": [ + "array", + "boolean", + "integer", + "number", + "null", + "object", + "string" + ] + } + }, + "properties": { + "ClearConfig": { + "description": "This indicates whether to update firmware configuration or not.", + "longDescription": "The value of this property is used to indicate the firmware configuration update.", + "readonly": false, + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "Oem": { + "additionalProperties": true, + "description": "OemUpdateService Oem properties.", + "patternProperties": { + "^([a-zA-Z_][a-zA-Z0-9_]*)?@(odata|Redfish|Message)\\.[a-zA-Z_][a-zA-Z0-9_]*$": { + "description": "This property shall specify a valid odata or Redfish property.", + "type": [ + "array", + "boolean", + "integer", + "number", + "null", + "object", + "string" + ] + } + }, + "properties": { + "ApplyOptions": { + "anyOf": [ + { + "$ref": "#/definitions/ApplyOptions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + } + }, + "title": "#OemUpdateService" +} diff --git a/static/redfish/v1/schema/OemUpdateService_v1.xml b/static/redfish/v1/schema/OemUpdateService_v1.xml new file mode 100644 index 0000000..cbb7aa4 --- /dev/null +++ b/static/redfish/v1/schema/OemUpdateService_v1.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.17.1