Cherry-pick UpdateService Support for MultipartHttpPushUri
Upstream commit info here:
https://github.com/openbmc/bmcweb/commit/0ed80c8ce93a38eca6951ffad5c3143a3a720053
Since a service support the MultipartHttpPushUri property within the
UpdateService resource, so add a new MultipartHttpPushUri property,
This property shall contain a URI used to perform a Redfish
Specification-defined Multipart HTTP or HTTPS POST of a software image
for the purpose of installing software contained within the image, and
use the requirements of a multipart/form-data to request body for HTTP
push software update.
Tested:
curl -k -H "X-Auth-Token: $token" -H "Content-Type:
multipart/form-data"
-F
'UpdateParameters={"Targets":["/redfish/v1/Managers/bmc"],
"@Redfish.OperationApplyTime":"Immediate"};type=application/json'
-F
'UpdateFile=@obmc-phosphor-image.static.mtd.tar;type=application/
octet-stream'
https://${bmc}/redfish/v1/UpdateService/update
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "0",
"TaskState": "Running",
"TaskStatus": "OK"
}
Passed the validator:
Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: Ie1ea0594337efdb073270aba6918389c4381a2b3
(cherry picked from commit 0ed80c8ce93a38eca6951ffad5c3143a3a720053)
Google-Bug-Id: 290981635
Change-Id: Idadc06f6ba485523453e67d23a93d65e779ffc7a
Signed-off-by: Edward Lee <edwarddl@google.com>
diff --git a/recipes-phosphor/interfaces/bmcweb/0001-UpdateService-Support-for-MultipartHttpPushUri.patch b/recipes-phosphor/interfaces/bmcweb/0001-UpdateService-Support-for-MultipartHttpPushUri.patch
new file mode 100644
index 0000000..3f8dc9e
--- /dev/null
+++ b/recipes-phosphor/interfaces/bmcweb/0001-UpdateService-Support-for-MultipartHttpPushUri.patch
@@ -0,0 +1,317 @@
+From f27a664969be38d9ff58d811bd4c23f920d8199b Mon Sep 17 00:00:00 2001
+From: George Liu <liuxiwei@inspur.com>
+Date: Tue, 12 May 2020 16:06:27 +0800
+Subject: [PATCH] UpdateService: Support for MultipartHttpPushUri
+
+Since a service support the MultipartHttpPushUri property within the
+UpdateService resource, so add a new MultipartHttpPushUri property,
+This property shall contain a URI used to perform a Redfish
+Specification-defined Multipart HTTP or HTTPS POST of a software image
+for the purpose of installing software contained within the image, and
+use the requirements of a multipart/form-data to request body for HTTP
+push software update.
+
+Tested:
+curl -k -H "X-Auth-Token: $token" -H "Content-Type:
+multipart/form-data"
+-F
+'UpdateParameters={"Targets":["/redfish/v1/Managers/bmc"],
+"@Redfish.OperationApplyTime":"Immediate"};type=application/json'
+-F
+'UpdateFile=@obmc-phosphor-image.static.mtd.tar;type=application/
+octet-stream'
+https://${bmc}/redfish/v1/UpdateService/update
+{
+ "@odata.id": "/redfish/v1/TaskService/Tasks/0",
+ "@odata.type": "#Task.v1_4_3.Task",
+ "Id": "0",
+ "TaskState": "Running",
+ "TaskStatus": "OK"
+}
+
+Passed the validator:
+
+Patch Tracking Bug: b/290981635
+Upstream info / review: https://github.com/openbmc/bmcweb/commit/0ed80c8ce93a38eca6951ffad5c3143a3a720053
+Upstream-Status: Accepted
+Justification:
+
+Already submitted upstream but need downstream until rebase.
+
+Signed-off-by: George Liu <liuxiwei@inspur.com>
+Change-Id: Ie1ea0594337efdb073270aba6918389c4381a2b3
+(cherry picked from commit 0ed80c8ce93a38eca6951ffad5c3143a3a720053)
+---
+ redfish-core/lib/update_service.hpp | 214 ++++++++++++++++++++++------
+ 1 file changed, 167 insertions(+), 47 deletions(-)
+
+diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
+index 6d0e1502..cdac33e2 100644
+--- a/redfish-core/lib/update_service.hpp
++++ b/redfish-core/lib/update_service.hpp
+@@ -20,6 +20,7 @@
+ #include "app.hpp"
+ #include "dbus_utility.hpp"
+ #include "other_software_service.hpp"
++#include "multipart_parser.hpp"
+ #include "query.hpp"
+ #include "registries/privilege_registry.hpp"
+ #include "task.hpp"
+@@ -33,6 +34,7 @@
+ #include <sdbusplus/unpack_properties.hpp>
+
+ #include <array>
++#include <filesystem>
+ #include <string_view>
+
+ namespace redfish
+@@ -528,6 +530,150 @@ inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
+ });
+ }
+
++inline void uploadImageFile(crow::Response& res, std::string_view body)
++{
++ std::filesystem::path filepath(
++ "/tmp/images/" +
++ boost::uuids::to_string(boost::uuids::random_generator()()));
++ BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
++ std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
++ std::ofstream::trunc);
++ // set the permission of the file to 640
++ std::filesystem::perms permission =
++ std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
++ std::filesystem::permissions(filepath, permission);
++ out << body;
++
++ if (out.bad())
++ {
++ messages::internalError(res);
++ cleanUp();
++ }
++}
++
++inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
++ const std::string& 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;
++ }
++
++ // 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",
++ dbus::utility::DbusVariantType{applyTimeNewVal});
++}
++
++inline void
++ updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
++ const MultipartParser& parser)
++{
++ const std::string* uploadData = nullptr;
++ std::optional<std::string> applyTime = "OnReset";
++ bool targetFound = false;
++ for (const FormPart& formpart : parser.mime_fields)
++ {
++ boost::beast::http::fields::const_iterator it =
++ formpart.fields.find("Content-Disposition");
++ if (it == formpart.fields.end())
++ {
++ BMCWEB_LOG_ERROR << "Couldn't find Content-Disposition";
++ return;
++ }
++ BMCWEB_LOG_INFO << "Parsing value " << it->value();
++
++ // The construction parameters of param_list must start with `;`
++ size_t index = it->value().find(';');
++ if (index == std::string::npos)
++ {
++ continue;
++ }
++
++ for (auto const& param :
++ boost::beast::http::param_list{it->value().substr(index)})
++ {
++ if (param.first != "name" || param.second.empty())
++ {
++ continue;
++ }
++
++ if (param.second == "UpdateParameters")
++ {
++ std::vector<std::string> targets;
++ nlohmann::json content =
++ nlohmann::json::parse(formpart.content);
++ if (!json_util::readJson(content, asyncResp->res, "Targets",
++ targets, "@Redfish.OperationApplyTime",
++ applyTime))
++ {
++ return;
++ }
++ if (targets.size() != 1)
++ {
++ messages::propertyValueFormatError(asyncResp->res,
++ "Targets", "");
++ return;
++ }
++ if (targets[0] != "/redfish/v1/Managers/bmc")
++ {
++ messages::propertyValueNotInList(asyncResp->res,
++ "Targets/0", targets[0]);
++ return;
++ }
++ targetFound = true;
++ }
++ else if (param.second == "UpdateFile")
++ {
++ uploadData = &(formpart.content);
++ }
++ }
++ }
++
++ if (uploadData == nullptr)
++ {
++ BMCWEB_LOG_ERROR << "Upload data is NULL";
++ messages::propertyMissing(asyncResp->res, "UpdateFile");
++ return;
++ }
++ if (!targetFound)
++ {
++ messages::propertyMissing(asyncResp->res, "targets");
++ return;
++ }
++
++ setApplyTime(asyncResp, *applyTime);
++
++ uploadImageFile(asyncResp->res, *uploadData);
++}
++
+ inline void
+ handleUpdateServicePost(App& app, const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+@@ -541,15 +687,23 @@ inline void
+ // Setup callback for when new software detected
+ monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService");
+
+- std::string filepath(
+- "/tmp/images/" +
+- boost::uuids::to_string(boost::uuids::random_generator()()));
+- BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
+- std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
+- std::ofstream::trunc);
+- out << req.body();
+- out.close();
+- BMCWEB_LOG_DEBUG << "file upload complete!!";
++ MultipartParser parser;
++ ParserError ec = parser.parse(req);
++ if (ec == ParserError::ERROR_BOUNDARY_FORMAT)
++ {
++ // If the request didnt' contain boundary information, assume it was a
++ // POST binary payload.
++ uploadImageFile(asyncResp->res, req.body());
++ return;
++ }
++ if (ec != ParserError::PARSER_SUCCESS)
++ {
++ // handle error
++ BMCWEB_LOG_ERROR << "MIME parse failed, ec : " << static_cast<int>(ec);
++ messages::internalError(asyncResp->res);
++ return;
++ }
++ updateMultipartContext(asyncResp, parser);
+ }
+
+ inline void requestRoutesUpdateService(App& app)
+@@ -564,7 +718,7 @@ inline void requestRoutesUpdateService(App& app)
+ return;
+ }
+ asyncResp->res.jsonValue["@odata.type"] =
+- "#UpdateService.v1_5_0.UpdateService";
++ "#UpdateService.v1_11_1.UpdateService";
+ asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
+ asyncResp->res.jsonValue["Id"] = "UpdateService";
+ asyncResp->res.jsonValue["Description"] = "Service for Software Update";
+@@ -581,6 +735,8 @@ inline void requestRoutesUpdateService(App& app)
+
+ asyncResp->res.jsonValue["HttpPushUri"] =
+ "/redfish/v1/UpdateService/update";
++ asyncResp->res.jsonValue["MultipartHttpPushUri"] =
++ "/redfish/v1/UpdateService/update";
+
+ // UpdateService cannot be disabled
+ asyncResp->res.jsonValue["ServiceEnabled"] = true;
+@@ -668,43 +824,7 @@ inline void requestRoutesUpdateService(App& app)
+
+ if (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;
+- }
+-
+- // 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",
+- dbus::utility::DbusVariantType{applyTimeNewVal});
++ setApplyTime(asyncResp, *applyTime);
+ }
+ }
+ }
+--
+2.41.0.255.g8b1d071c50-goog
+
diff --git a/recipes-phosphor/interfaces/bmcweb_%.bbappend b/recipes-phosphor/interfaces/bmcweb_%.bbappend
index ebb0655..23f711f 100644
--- a/recipes-phosphor/interfaces/bmcweb_%.bbappend
+++ b/recipes-phosphor/interfaces/bmcweb_%.bbappend
@@ -99,6 +99,7 @@
file://0001-Expose-Systems-in-ComputerSystemCollection-by-Item.S.patch \
file://0001-Add-Multi-Host-Power-Control-Support.patch \
file://0001-Add-Multi-Host-Support-for-HostLogger.patch \
+ file://0001-UpdateService-Support-for-MultipartHttpPushUri.patch \
"
multi_host_patches:gbmcfork = ""
SRC_URI:append:gbmc = "${multi_host_patches}"