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> &params) 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> &params) 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;