From c65d6f4a6d2939335608957fba25e5c8a445813e Mon Sep 17 00:00:00 2001 From: Vikram Bodireddy Date: Mon, 28 Jun 2021 21:56:18 +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 --- redfish-core/lib/update_service.hpp | 456 ++++++++++++++---- static/redfish/v1/$metadata/index.xml | 3 + .../JsonSchemas/OemUpdateService/index.json | 69 +++ .../redfish/v1/schema/OemUpdateService_v1.xml | 40 ++ 4 files changed, 481 insertions(+), 87 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 663d48b..70c58d4 100644 --- a/redfish-core/lib/update_service.hpp +++ b/redfish-core/lib/update_service.hpp @@ -26,7 +26,9 @@ namespace redfish { - +// params for multiple firmware targets +std::vector httpPushUriTargets; +bool httpPushUriTargetBusy = false; // Match signals added on software path static std::unique_ptr fwUpdateMatcher; static std::unique_ptr fwUpdateErrorMatcher; @@ -34,6 +36,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"; inline static void cleanUp() { @@ -42,28 +55,120 @@ inline static void cleanUp() fwUpdateErrorMatcher = nullptr; } inline 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()) + { + return; + } + + for (const auto& [invObjPath, invDict] : subtree) { - BMCWEB_LOG_DEBUG << "error_code = " << errorCode; - BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message(); + 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) { @@ -76,22 +181,24 @@ static void 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) @@ -118,7 +225,7 @@ static void // is added fwAvailableTimer = nullptr; - activateImage(objPath.str, objInfo[0].first); + activateImage(objPath.str, objInfo[0].first, imgTargets); if (asyncResp) { std::shared_ptr task = @@ -250,8 +357,7 @@ static void "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}); } } } @@ -261,7 +367,7 @@ static void static void monitorForSoftwareAvailable( const std::shared_ptr& asyncResp, const crow::Request& req, const std::string& url, - int timeoutTimeSeconds = 10) + const std::vector& imgUriTargets, int timeoutTimeSeconds = 10) { // Only allow one FW update at a time if (fwUpdateInProgress != false) @@ -301,9 +407,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; @@ -468,12 +575,15 @@ inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app) 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 @@ -522,6 +632,9 @@ inline void requestRoutesUpdateService(App& app) asyncResp->res.jsonValue["Name"] = "Update Service"; asyncResp->res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; + asyncResp->res.jsonValue["HttpPushUriTargets"] = httpPushUriTargets; + asyncResp->res.jsonValue["HttpPushUriTargetsBusy"] = + httpPushUriTargetBusy; // UpdateService cannot be disabled asyncResp->res.jsonValue["ServiceEnabled"] = true; asyncResp->res.jsonValue["FirmwareInventory"] = { @@ -536,7 +649,8 @@ inline void requestRoutesUpdateService(App& app) asyncResp->res .jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; updateSvcSimpleUpdate["target"] = - "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; + "/redfish/v1/UpdateService/Actions/" + "UpdateService.SimpleUpdate"; updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {"TFTP"}; #endif @@ -578,89 +692,258 @@ inline void requestRoutesUpdateService(App& app) "/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( + [asyncResp](const boost::system::error_code ec, + const std::variant applyOption) { + if (ec) + { + BMCWEB_LOG_DEBUG << "DBUS response error " << ec; + messages::internalError(asyncResp->res); + return; + } + + const bool* b = std::get_if(&applyOption); + + if (b) + { + asyncResp->res + .jsonValue["Oem"]["ApplyOptions"]["@odata.type"] = + "#OemUpdateService.ApplyOptions"; + asyncResp->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"); }); + BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") .privileges(redfish::privileges::patchUpdateService) - .methods(boost::beast::http::verb::patch)( - [](const crow::Request& req, - const std::shared_ptr& asyncResp) { - BMCWEB_LOG_DEBUG << "doPatch..."; + .methods( + boost::beast::http::verb:: + patch)([](const crow::Request& req, + const std::shared_ptr& asyncResp) { + BMCWEB_LOG_DEBUG << "doPatch..."; + + std::optional pushUriOptions; + std::optional> imgTargets; + std::optional imgTargetBusy; + std::optional oemProps; + if (!json_util::readJson(req, asyncResp->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, asyncResp->res, + "ApplyOptions", applyOptions)) + { + return; + } + + if (applyOptions) + { + std::optional clearConfig; + if (!json_util::readJson(*applyOptions, asyncResp->res, + "ClearConfig", clearConfig)) + { + return; + } - std::optional pushUriOptions; - if (!json_util::readJson(req, asyncResp->res, - "HttpPushUriOptions", pushUriOptions)) + 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; + if (!json_util::readJson(*pushUriOptions, asyncResp->res, + "HttpPushUriApplyTime", + pushUriApplyTime)) { return; } - if (pushUriOptions) + if (pushUriApplyTime) { - std::optional pushUriApplyTime; - if (!json_util::readJson(*pushUriOptions, asyncResp->res, - "HttpPushUriApplyTime", - pushUriApplyTime)) + std::optional applyTime; + if (!json_util::readJson(*pushUriApplyTime, asyncResp->res, + "ApplyTime", applyTime)) { return; } - if (pushUriApplyTime) + if (applyTime) { - std::optional applyTime; - if (!json_util::readJson(*pushUriApplyTime, - asyncResp->res, "ApplyTime", - applyTime)) + std::string applyTimeNewVal; + if (applyTime == "Immediate") + { + applyTimeNewVal = + "xyz.openbmc_project.Software.ApplyTime." + "RequestedApplyTimes.Immediate"; + } + else if (applyTime == "OnReset") { + applyTimeNewVal = + "xyz.openbmc_project.Software.ApplyTime." + "RequestedApplyTimes.OnReset"; + } + else + { + BMCWEB_LOG_INFO + << "ApplyTime value is not in the list of " + "acceptable values"; + messages::propertyValueNotInList( + asyncResp->res, *applyTime, "ApplyTime"); return; } - if (applyTime) + // 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.Settings", + "/xyz/openbmc_project/software/apply_time", + "org.freedesktop.DBus.Properties", "Set", + "xyz.openbmc_project.Software.ApplyTime", + "RequestedApplyTime", + std::variant{applyTimeNewVal}); + } + } + } + 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) { - std::string applyTimeNewVal; - if (applyTime == "Immediate") - { - applyTimeNewVal = - "xyz.openbmc_project.Software.ApplyTime." - "RequestedApplyTimes.Immediate"; - } - else if (applyTime == "OnReset") - { - applyTimeNewVal = - "xyz.openbmc_project.Software.ApplyTime." - "RequestedApplyTimes.OnReset"; - } - else - { - BMCWEB_LOG_INFO - << "ApplyTime value is not in the list of " - "acceptable values"; - messages::propertyValueNotInList( - asyncResp->res, *applyTime, "ApplyTime"); - return; - } + messages::invalidObject(asyncResp->res, + "HttpPushUriTargets"); + return; + } + crow::connections::systemBus->async_method_call( + [asyncResp, uriTargets{*imgTargets}, + targetBusy{*imgTargetBusy}]( + const boost::system::error_code ec, + const std::vector swInvPaths) { + if (ec) + { + return; + } - // Set the requested image apply time value - crow::connections::systemBus->async_method_call( - [asyncResp]( - const boost::system::error_code ec) { - if (ec) + bool swInvObjFound = false; + for (const std::string& path : swInvPaths) + { + std::size_t idPos = path.rfind("/"); + if ((idPos == std::string::npos) || + ((idPos + 1) >= path.size())) { - BMCWEB_LOG_ERROR - << "D-Bus responses error: " << ec; messages::internalError(asyncResp->res); + BMCWEB_LOG_DEBUG + << "Can't parse firmware ID!!"; return; } - messages::success(asyncResp->res); - }, - "xyz.openbmc_project.Settings", - "/xyz/openbmc_project/software/apply_time", - "org.freedesktop.DBus.Properties", "Set", - "xyz.openbmc_project.Software.ApplyTime", - "RequestedApplyTime", - std::variant{applyTimeNewVal}); - } + std::string swId = path.substr(idPos + 1); + + if (swId == uriTargets[0]) + { + swInvObjFound = true; + break; + } + } + if (!swInvObjFound) + { + messages::invalidObject( + asyncResp->res, "HttpPushUriTargets"); + return; + } + httpPushUriTargetBusy = targetBusy; + 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; + } + } + }); + BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") .privileges(redfish::privileges::postUpdateService) .methods(boost::beast::http::verb::post)( @@ -670,7 +953,8 @@ inline void requestRoutesUpdateService(App& app) // Setup callback for when new software detected monitorForSoftwareAvailable(asyncResp, req, - "/redfish/v1/UpdateService"); + "/redfish/v1/UpdateService", + httpPushUriTargets); std::string filepath("/tmp/images/" + boost::uuids::to_string( @@ -683,7 +967,7 @@ inline void requestRoutesUpdateService(App& app) out.close(); BMCWEB_LOG_DEBUG << "file upload complete!!"; }); -} +} // namespace redfish inline void requestRoutesSoftwareInventoryCollection(App& app) { @@ -746,8 +1030,7 @@ inline void requestRoutesSoftwareInventoryCollection(App& app) "/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}); }); } /* Fill related item links (i.e. bmc, bios) in for inventory */ @@ -911,7 +1194,7 @@ inline void requestRoutesSoftwareInventory(App& app) }, obj.second[0].first, obj.first, "org.freedesktop.DBus.Properties", "GetAll", - "xyz.openbmc_project.Software.Version"); + versionIntf); } if (!found) { @@ -935,8 +1218,7 @@ inline void requestRoutesSoftwareInventory(App& app) "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", static_cast(0), - std::array{ - "xyz.openbmc_project.Software.Version"}); + std::array{versionIntf}); }); } diff --git a/static/redfish/v1/$metadata/index.xml b/static/redfish/v1/$metadata/index.xml index eba38bf..876ebfb 100644 --- a/static/redfish/v1/$metadata/index.xml +++ b/static/redfish/v1/$metadata/index.xml @@ -2346,6 +2346,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