Implement POST for redfish UpdateService
- POST an image file to /redfish/v1/UpdateServer uri will upload the
image and activate it
- Modified SoftwareInventoryCollection to list items with
xyz.openbmc_project.Software.Activation/Activation property as
"xyz.openbmc_project.Software.Activation.Activations.Active"
- SoftwareInventory odata.id is identified with DBus generated uuid
Signed-off-by: Jennifer Lee <jennifer1.lee@intel.com>
Change-Id: I48c3e6d868d1c27420cab5a706e4205f06713923
diff --git a/crow/include/crow/http_connection.h b/crow/include/crow/http_connection.h
index 36c19d0..691e380 100644
--- a/crow/include/crow/http_connection.h
+++ b/crow/include/crow/http_connection.h
@@ -248,6 +248,10 @@
#ifdef BMCWEB_ENABLE_DEBUG
static std::atomic<int> connectionCount;
#endif
+
+// request body limit size: 30M
+constexpr unsigned int httpReqBodyLimit = 1024 * 1024 * 30;
+
template <typename Adaptor, typename Handler, typename... Middlewares>
class Connection {
public:
@@ -264,8 +268,9 @@
getCachedDateStr(get_cached_date_str_f),
timerQueue(timerQueue) {
parser.emplace(std::piecewise_construct, std::make_tuple());
-
- parser->body_limit(1024 * 1024 * 1); // 1MB
+ // Temporarily changed to 30MB; Need to modify uploading/authentication
+ // mechanism
+ parser->body_limit(httpReqBodyLimit);
req.emplace(parser->get());
#ifdef BMCWEB_ENABLE_DEBUG
connectionCount++;
@@ -503,6 +508,8 @@
BMCWEB_LOG_DEBUG << this << " Clearing response";
res.clear();
parser.emplace(std::piecewise_construct, std::make_tuple());
+ parser->body_limit(httpReqBodyLimit); // reset body limit for
+ // newly created parser
buffer.consume(buffer.size());
req.emplace(parser->get());
diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
index 2ffa82c..efa0fbf 100644
--- a/redfish-core/lib/update_service.hpp
+++ b/redfish-core/lib/update_service.hpp
@@ -19,11 +19,12 @@
#include <boost/container/flat_map.hpp>
namespace redfish {
+static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
class OnDemandSoftwareInventoryProvider {
public:
template <typename CallbackFunc>
- void get_all_software_inventory_object(CallbackFunc &&callback) {
+ void getAllSoftwareInventoryObject(CallbackFunc &&callback) {
crow::connections::systemBus->async_method_call(
[callback{std::move(callback)}](
const boost::system::error_code error_code,
@@ -53,7 +54,7 @@
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree",
"/xyz/openbmc_project/software", int32_t(1),
- std::array<const char *, 1>{"xyz.openbmc_project.Software.Version"});
+ std::array<const char *, 1>{"xyz.openbmc_project.Software.Activation"});
}
};
@@ -67,6 +68,7 @@
Node::json["Id"] = "UpdateService";
Node::json["Description"] = "Service for Software Update";
Node::json["Name"] = "Update Service";
+ Node::json["HttpPushUri"] = "/redfish/v1/UpdateService";
Node::json["ServiceEnabled"] = true; // UpdateService cannot be disabled
Node::json["FirmwareInventory"] = {
{"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}};
@@ -86,6 +88,115 @@
res.jsonValue = Node::json;
res.end();
}
+ static void activateImage(const std::string &obj_path) {
+ crow::connections::systemBus->async_method_call(
+ [obj_path](const boost::system::error_code error_code) {
+ if (error_code) {
+ BMCWEB_LOG_DEBUG << "error_code = " << error_code;
+ BMCWEB_LOG_DEBUG << "error msg = " << error_code.message();
+ }
+ },
+ "xyz.openbmc_project.Software.BMC.Updater", obj_path,
+ "org.freedesktop.DBus.Properties", "Set",
+ "xyz.openbmc_project.Software.Activation", "RequestedActivation",
+ sdbusplus::message::variant<std::string>(
+ "xyz.openbmc_project.Software.Activation.RequestedActivations."
+ "Active"));
+ }
+ void doPost(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> ¶ms) override {
+ BMCWEB_LOG_DEBUG << "doPost...";
+
+ // Only allow one FW update at a time
+ if (fwUpdateMatcher != nullptr) {
+ res.addHeader("Retry-After", "30");
+ res.result(boost::beast::http::status::service_unavailable);
+ res.jsonValue = messages::serviceTemporarilyUnavailable("3");
+ res.end();
+ return;
+ }
+ // Make this const static so it survives outside this method
+ static boost::asio::deadline_timer timeout(*req.ioService,
+ boost::posix_time::seconds(5));
+
+ timeout.expires_from_now(boost::posix_time::seconds(5));
+
+ timeout.async_wait([&res](const boost::system::error_code &ec) {
+ fwUpdateMatcher = nullptr;
+ if (ec == boost::asio::error::operation_aborted) {
+ // expected, we were canceled before the timer completed.
+ return;
+ }
+ BMCWEB_LOG_ERROR << "Timed out waiting for firmware object being created";
+ BMCWEB_LOG_ERROR << "FW image may has already been uploaded to server";
+ if (ec) {
+ BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
+ return;
+ }
+
+ res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = redfish::messages::internalError();
+ res.end();
+ });
+
+ auto callback = [&res](sdbusplus::message::message &m) {
+ BMCWEB_LOG_DEBUG << "Match fired";
+ bool flag = false;
+
+ if (m.is_method_error()) {
+ BMCWEB_LOG_DEBUG << "Dbus method error!!!";
+ res.end();
+ return;
+ }
+ std::vector<std::pair<
+ std::string,
+ std::vector<std::pair<std::string,
+ sdbusplus::message::variant<std::string>>>>>
+ interfaces_properties;
+
+ sdbusplus::message::object_path obj_path;
+
+ m.read(obj_path, interfaces_properties); // Read in the object path
+ // that was just created
+ // std::string str_objpath = obj_path.str; // keep a copy for
+ // constructing response message
+ BMCWEB_LOG_DEBUG << "obj path = " << obj_path.str; // str_objpath;
+ for (auto &interface : interfaces_properties) {
+ BMCWEB_LOG_DEBUG << "interface = " << interface.first;
+
+ if (interface.first == "xyz.openbmc_project.Software.Activation") {
+ // cancel timer only when xyz.openbmc_project.Software.Activation
+ // interface is added
+ boost::system::error_code ec;
+ timeout.cancel(ec);
+ if (ec) {
+ BMCWEB_LOG_ERROR << "error canceling timer " << ec;
+ }
+ UpdateService::activateImage(obj_path.str); // str_objpath);
+ res.jsonValue = redfish::messages::success();
+ BMCWEB_LOG_DEBUG << "ending response";
+ res.end();
+ fwUpdateMatcher = nullptr;
+ }
+ }
+ };
+
+ fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
+ *crow::connections::systemBus,
+ "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
+ "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
+ callback);
+
+ 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!!";
+ }
};
class SoftwareInventoryCollection : public Node {
@@ -120,7 +231,7 @@
void doGet(crow::Response &res, const crow::Request &req,
const std::vector<std::string> ¶ms) override {
res.jsonValue = Node::json;
- softwareInventoryProvider.get_all_software_inventory_object(
+ softwareInventoryProvider.getAllSoftwareInventoryObject(
[&](const bool &success,
const std::vector<std::pair<
std::string,
@@ -128,61 +239,83 @@
&subtree) {
if (!success) {
res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
res.end();
return;
}
if (subtree.empty()) {
BMCWEB_LOG_DEBUG << "subtree empty!!";
+ res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
res.end();
return;
}
res.jsonValue["Members"] = nlohmann::json::array();
+ res.jsonValue["Members@odata.count"] = 0;
+
+ std::shared_ptr<AsyncResp> asyncResp =
+ std::make_shared<AsyncResp>(res);
for (auto &obj : subtree) {
const std::vector<std::pair<std::string, std::vector<std::string>>>
&connections = obj.second;
- for (auto &conn : connections) {
+ // if can't parse fw id then return
+ std::size_t id_pos;
+ if ((id_pos = obj.first.rfind("/")) == std::string::npos) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
+ BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
+ res.end();
+ return;
+ }
+ std::string fw_id = obj.first.substr(id_pos + 1);
+
+ for (const auto &conn : connections) {
const std::string connectionName = conn.first;
BMCWEB_LOG_DEBUG << "connectionName = " << connectionName;
BMCWEB_LOG_DEBUG << "obj.first = " << obj.first;
crow::connections::systemBus->async_method_call(
- [&](const boost::system::error_code error_code,
- const boost::container::flat_map<std::string, VariantType>
- &propertiesList) {
+ [asyncResp, fw_id](
+ const boost::system::error_code error_code,
+ const sdbusplus::message::variant<std::string>
+ &activation_status) {
BMCWEB_LOG_DEBUG << "safe returned in lambda function";
if (error_code) {
- res.result(
+ asyncResp->res.result(
boost::beast::http::status::internal_server_error);
- res.end();
+ asyncResp->res.jsonValue = messages::internalError();
+ asyncResp->res.end();
return;
}
- boost::container::flat_map<std::string,
- VariantType>::const_iterator it =
- propertiesList.find("Purpose");
- const std::string &sw_inv_purpose =
- *(mapbox::getPtr<const std::string>(it->second));
- std::size_t last_pos = sw_inv_purpose.rfind(".");
- if (last_pos != std::string::npos) {
- res.jsonValue["Members"].push_back(
- {{"@odata.id",
- "/redfish/v1/UpdateService/FirmwareInventory/" +
- sw_inv_purpose.substr(last_pos + 1)}});
- res.jsonValue["Members@odata.count"] =
- res.jsonValue["Members"].size();
- res.end();
+ const std::string *activation_status_str =
+ mapbox::getPtr<const std::string>(activation_status);
+ if (activation_status_str != nullptr &&
+ *activation_status_str !=
+ "xyz.openbmc_project.Software.Activation."
+ "Activations.Active") {
+ // The activation status of this software is not currently
+ // active, so does not need to be listed in the response
+ return;
}
-
+ asyncResp->res.jsonValue["Members"].push_back(
+ {{"@odata.id",
+ "/redfish/v1/UpdateService/FirmwareInventory/" +
+ fw_id}});
+ asyncResp->res.jsonValue["Members@odata.count"] =
+ asyncResp->res.jsonValue["Members"].size();
},
connectionName, obj.first, "org.freedesktop.DBus.Properties",
- "GetAll", "xyz.openbmc_project.Software.Version");
+ "Get", "xyz.openbmc_project.Software.Activation",
+ "Activation");
}
}
});
}
+
OnDemandSoftwareInventoryProvider softwareInventoryProvider;
};
/**
@@ -201,9 +334,10 @@
Node::json["@odata.context"] =
"/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory";
Node::json["Name"] = "Software Inventory";
- Node::json["Status"] = "OK"; // TODO
Node::json["Updateable"] = false;
-
+ Node::json["Status"]["Health"] = "OK";
+ Node::json["Status"]["HealthRollup"] = "OK";
+ Node::json["Status"]["State"] = "Enabled";
entityPrivileges = {
{boost::beast::http::verb::get, {{"Login"}}},
{boost::beast::http::verb::head, {{"Login"}}},
@@ -223,81 +357,124 @@
if (params.size() != 1) {
res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
res.end();
return;
}
- const std::string &sw_id = params[0];
+ const std::string &fw_id = params[0];
+ res.jsonValue["Id"] = fw_id;
res.jsonValue["@odata.id"] =
- "/redfish/v1/UpdateService/FirmwareInventory/" + sw_id;
- softwareInventoryProvider.get_all_software_inventory_object(
- [&, id{std::string(sw_id)} ](
- const bool &success,
- const std::vector<std::pair<
- std::string,
- std::vector<std::pair<std::string, std::vector<std::string>>>>>
- &subtree) {
- BMCWEB_LOG_DEBUG << "doGet callback...";
- if (!success) {
- res.result(boost::beast::http::status::internal_server_error);
- res.end();
- return;
- }
+ "/redfish/v1/UpdateService/FirmwareInventory/" + fw_id;
+ softwareInventoryProvider.getAllSoftwareInventoryObject([
+ &res, id{std::string(fw_id)}
+ ](const bool &success,
+ const std::vector<std::pair<
+ std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>>
+ &subtree) {
+ BMCWEB_LOG_DEBUG << "doGet callback...";
+ if (!success) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
+ res.end();
+ return;
+ }
- if (subtree.empty()) {
- BMCWEB_LOG_DEBUG << "subtree empty!!";
- res.end();
- return;
- }
+ if (subtree.empty()) {
+ BMCWEB_LOG_ERROR << "subtree empty!!";
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ return;
+ }
- for (auto &obj : subtree) {
- const std::vector<std::pair<std::string, std::vector<std::string>>>
- &connections = obj.second;
+ bool fw_id_found = false;
- for (auto &conn : connections) {
- const std::string connectionName = conn.first;
- BMCWEB_LOG_DEBUG << "connectionName = " << connectionName;
- BMCWEB_LOG_DEBUG << "obj.first = " << obj.first;
+ for (auto &obj : subtree) {
+ if (boost::ends_with(obj.first, id) != true) {
+ continue;
+ }
+ fw_id_found = true;
- crow::connections::systemBus->async_method_call(
- [&, id{std::string(id)} ](
- const boost::system::error_code error_code,
- const boost::container::flat_map<std::string, VariantType>
- &propertiesList) {
- if (error_code) {
- res.result(
- boost::beast::http::status::internal_server_error);
- res.end();
- return;
- }
- boost::container::flat_map<std::string,
- VariantType>::const_iterator it =
- propertiesList.find("Purpose");
- if (it == propertiesList.end()) {
- BMCWEB_LOG_DEBUG << "Can't find property \"Purpose\"!";
- return;
- }
- const std::string &sw_inv_purpose =
- *(mapbox::getPtr<const std::string>(it->second));
- BMCWEB_LOG_DEBUG << "sw_inv_purpose = " << sw_inv_purpose;
- if (boost::ends_with(sw_inv_purpose, "." + id)) {
- it = propertiesList.find("Version");
- if (it == propertiesList.end()) {
- BMCWEB_LOG_DEBUG << "Can't find property \"Version\"!";
- return;
- }
- res.jsonValue["Version"] =
- *(mapbox::getPtr<const std::string>(it->second));
- res.jsonValue["Id"] = id;
- res.end();
- }
+ const std::vector<std::pair<std::string, std::vector<std::string>>>
+ &connections = obj.second;
- },
- connectionName, obj.first, "org.freedesktop.DBus.Properties",
- "GetAll", "xyz.openbmc_project.Software.Version");
- }
- }
- });
+ if (connections.size() <= 0) {
+ continue;
+ }
+ const std::pair<std::string, std::vector<std::string>> &conn =
+ connections[0];
+ const std::string &connectionName = conn.first;
+ BMCWEB_LOG_DEBUG << "connectionName = " << connectionName;
+ BMCWEB_LOG_DEBUG << "obj.first = " << obj.first;
+
+ crow::connections::systemBus->async_method_call(
+ [&res, id](
+ const boost::system::error_code error_code,
+ const boost::container::flat_map<std::string, VariantType>
+ &propertiesList) {
+ if (error_code) {
+ res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
+ res.end();
+ return;
+ }
+
+ boost::container::flat_map<std::string,
+ VariantType>::const_iterator it =
+ propertiesList.find("Purpose");
+ if (it == propertiesList.end()) {
+ BMCWEB_LOG_ERROR << "Can't find property \"Purpose\"!";
+ res.result(boost::beast::http::status::internal_server_error);
+ res.jsonValue = messages::internalError();
+ res.end();
+ return;
+ }
+
+ // SoftwareId
+ const std::string *sw_inv_purpose =
+ mapbox::getPtr<const std::string>(it->second);
+ if (sw_inv_purpose == nullptr) {
+ res.jsonValue = redfish::messages::internalError();
+ res.jsonValue = messages::internalError();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "sw_inv_purpose = " << sw_inv_purpose;
+ std::size_t last_pos = sw_inv_purpose->rfind(".");
+ if (last_pos != std::string::npos) {
+ res.jsonValue["SoftwareId"] =
+ sw_inv_purpose->substr(last_pos + 1);
+ } else {
+ BMCWEB_LOG_ERROR << "Can't parse software purpose!";
+ }
+
+ // Version
+ it = propertiesList.find("Version");
+ if (it != propertiesList.end()) {
+ const std::string *version =
+ mapbox::getPtr<const std::string>(it->second);
+ if (version == nullptr) {
+ res.jsonValue = redfish::messages::internalError();
+ res.jsonValue = messages::internalError();
+ return;
+ }
+ res.jsonValue["Version"] =
+ *(mapbox::getPtr<const std::string>(it->second));
+ } else {
+ BMCWEB_LOG_DEBUG << "Can't find version info!";
+ }
+
+ res.end();
+ },
+ connectionName, obj.first, "org.freedesktop.DBus.Properties",
+ "GetAll", "xyz.openbmc_project.Software.Version");
+ }
+ if (!fw_id_found) {
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ return;
+ }
+ });
}
OnDemandSoftwareInventoryProvider softwareInventoryProvider;