Single FRU VPD collection

This commit implements a Dbus Api to collect VPD of a given FRU
by launching the parser exe asynchronously.

The caller is supposed to update Present property for that FRU
as false on Dbus before calling this Api and need to listen for
Present property change signal to mark the collection of VPD
complete for the FRU.

One of the use case for this api is to collect VPD for FRU
undergoing concurrent maintenance.

Signed-off-by: Sunny Srivastava <sunnsr25@in.ibm.com>
Change-Id: Idc44d6bc05deb04f3b9097ccf3129f5d2b11eaf0
diff --git a/vpd-manager/manager.cpp b/vpd-manager/manager.cpp
index 4c8b416..d3e7e9c 100644
--- a/vpd-manager/manager.cpp
+++ b/vpd-manager/manager.cpp
@@ -74,6 +74,11 @@
             this->deleteFRUVPD(path);
         });
 
+    interface->register_method(
+        "CollectFRUVPD", [this](const sdbusplus::message::object_path& path) {
+            this->collectFRUVPD(path);
+        });
+
     sd_bus_default(&sdBus);
     initManager();
 }
@@ -508,64 +513,8 @@
             prePostActionRequired = true;
         }
 
-        if ((singleFru.find("devAddress") == singleFru.end()) ||
-            (singleFru.find("driverType") == singleFru.end()) ||
-            (singleFru.find("busType") == singleFru.end()))
-        {
-            // The FRUs is marked for replacement but missing mandatory
-            // fields for recollection. Skip to another replaceable fru.
-            log<level::ERR>(
-                "Recollection Failed as mandatory field missing in Json",
-                entry("ERROR=%s",
-                      ("Recollection failed for " + inventoryPath).c_str()));
-            continue;
-        }
-
-        string str = "echo ";
-        string deviceAddress = singleFru["devAddress"];
-        const string& driverType = singleFru["driverType"];
-        const string& busType = singleFru["busType"];
-
-        // devTreeStatus flag is present in json as false to mention
-        // that the EEPROM is not mentioned in device tree. If this flag
-        // is absent consider the value to be true, i.e EEPROM is
-        // mentioned in device tree
-        if (!singleFru.value("devTreeStatus", true))
-        {
-            auto pos = deviceAddress.find('-');
-            if (pos != string::npos)
-            {
-                string busNum = deviceAddress.substr(0, pos);
-                deviceAddress =
-                    "0x" + deviceAddress.substr(pos + 1, string::npos);
-
-                string deleteDevice = str + deviceAddress + " > /sys/bus/" +
-                                      busType + "/devices/" + busType + "-" +
-                                      busNum + "/delete_device";
-                executeCmd(deleteDevice);
-
-                string addDevice = str + driverType + " " + deviceAddress +
-                                   " > /sys/bus/" + busType + "/devices/" +
-                                   busType + "-" + busNum + "/new_device";
-                executeCmd(addDevice);
-            }
-            else
-            {
-                log<level::ERR>(
-                    "Wrong format of device address in Json",
-                    entry(
-                        "ERROR=%s",
-                        ("Recollection failed for " + inventoryPath).c_str()));
-                continue;
-            }
-        }
-        else
-        {
-            executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
-                                                  driverType, "/unbind"));
-            executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
-                                                  driverType, "/bind"));
-        }
+        // unbind, bind the driver to trigger parser.
+        triggerVpdCollection(singleFru, inventoryPath);
 
         // this check is added to avoid file system expensive call in case not
         // required.
@@ -591,6 +540,134 @@
     }
 }
 
+void Manager::collectFRUVPD(const sdbusplus::message::object_path& path)
+{
+    using InvalidArgument =
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+    using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+    // if path not found in Json.
+    if (frus.find(path) == frus.end())
+    {
+        elog<InvalidArgument>(
+            Argument::ARGUMENT_NAME("Object Path"),
+            Argument::ARGUMENT_VALUE(std::string(path).c_str()));
+    }
+
+    inventory::Path vpdFilePath = std::get<0>(frus.find(path)->second);
+
+    const std::vector<nlohmann::json>& groupEEPROM =
+        jsonFile["frus"][vpdFilePath].get_ref<const nlohmann::json::array_t&>();
+
+    const nlohmann::json& singleFru = groupEEPROM[0];
+
+    // check if the device qualifies for CM.
+    if (singleFru.value("concurrentlyMaintainable", false))
+    {
+        bool prePostActionRequired = false;
+
+        if ((jsonFile["frus"][vpdFilePath].at(0)).find("preAction") !=
+            jsonFile["frus"][vpdFilePath].at(0).end())
+        {
+            if (!executePreAction(jsonFile, vpdFilePath))
+            {
+                // if the FRU has preAction defined then its execution should
+                // pass to ensure bind/unbind of data.
+                // preAction execution failed. should not call bind/unbind.
+                log<level::ERR>("Pre-Action execution failed for the FRU");
+                return;
+            }
+
+            prePostActionRequired = true;
+        }
+
+        // unbind, bind the driver to trigger parser.
+        triggerVpdCollection(singleFru, std::string(path));
+
+        // this check is added to avoid file system expensive call in case not
+        // required.
+        if (prePostActionRequired)
+        {
+            // Check if device showed up (test for file)
+            if (!filesystem::exists(vpdFilePath))
+            {
+                // If not, then take failure postAction
+                executePostFailAction(jsonFile, vpdFilePath);
+            }
+        }
+        return;
+    }
+    else
+    {
+        elog<InvalidArgument>(
+            Argument::ARGUMENT_NAME("Object Path"),
+            Argument::ARGUMENT_VALUE(std::string(path).c_str()));
+    }
+}
+
+void Manager::triggerVpdCollection(const nlohmann::json& singleFru,
+                                   const std::string& path)
+{
+    if ((singleFru.find("devAddress") == singleFru.end()) ||
+        (singleFru.find("driverType") == singleFru.end()) ||
+        (singleFru.find("busType") == singleFru.end()))
+    {
+        // The FRUs is marked for collection but missing mandatory
+        // fields for collection. Log error and return.
+        log<level::ERR>(
+            "Collection Failed as mandatory field missing in Json",
+            entry("ERROR=%s", ("Recollection failed for " + (path)).c_str()));
+
+        return;
+    }
+
+    string deviceAddress = singleFru["devAddress"];
+    const string& driverType = singleFru["driverType"];
+    const string& busType = singleFru["busType"];
+
+    // devTreeStatus flag is present in json as false to mention
+    // that the EEPROM is not mentioned in device tree. If this flag
+    // is absent consider the value to be true, i.e EEPROM is
+    // mentioned in device tree
+    if (!singleFru.value("devTreeStatus", true))
+    {
+        auto pos = deviceAddress.find('-');
+        if (pos != string::npos)
+        {
+            string busNum = deviceAddress.substr(0, pos);
+            deviceAddress = "0x" + deviceAddress.substr(pos + 1, string::npos);
+
+            string deleteDevice = "echo" + deviceAddress + " > /sys/bus/" +
+                                  busType + "/devices/" + busType + "-" +
+                                  busNum + "/delete_device";
+            executeCmd(deleteDevice);
+
+            string addDevice = "echo" + driverType + " " + deviceAddress +
+                               " > /sys/bus/" + busType + "/devices/" +
+                               busType + "-" + busNum + "/new_device";
+            executeCmd(addDevice);
+        }
+        else
+        {
+            const string& inventoryPath =
+                singleFru["inventoryPath"]
+                    .get_ref<const nlohmann::json::string_t&>();
+
+            log<level::ERR>(
+                "Wrong format of device address in Json",
+                entry("ERROR=%s",
+                      ("Recollection failed for " + inventoryPath).c_str()));
+        }
+    }
+    else
+    {
+        executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
+                                              driverType, "/unbind"));
+        executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
+                                              driverType, "/bind"));
+    }
+}
+
 void Manager::deleteFRUVPD(const sdbusplus::message::object_path& path)
 {
     using InvalidArgument =