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/ibm_vpd_app.cpp b/ibm_vpd_app.cpp
index b625568..410e935 100644
--- a/ibm_vpd_app.cpp
+++ b/ibm_vpd_app.cpp
@@ -22,7 +22,6 @@
 #include <gpiod.hpp>
 #include <iostream>
 #include <iterator>
-#include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
 #include <regex>
 #include <thread>
diff --git a/test/meson.build b/test/meson.build
index c9ddf1b..0e1121f 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -11,8 +11,10 @@
 else
   dynamic_linker = []
 endif
+gmock = dependency('gmock', disabler: true, required: build_tests)
+gtest = dependency('gtest', main: true, disabler: true, required: build_tests)
 libgpiodcxx = dependency('libgpiodcxx')
-dependecy_list = [gtest_dep, gmock_dep, sdbusplus, phosphor_logging, phosphor_dbus_interfaces, libgpiodcxx]
+dependecy_list = [gtest, gmock, sdbusplus, phosphor_logging, phosphor_dbus_interfaces, libgpiodcxx]
 
 configuration_inc = include_directories('..', '../vpd-manager', 'vpd-manager-test', '../vpd-parser')
 
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 =
diff --git a/vpd-manager/manager.hpp b/vpd-manager/manager.hpp
index 13de42e..a0a9ca6 100644
--- a/vpd-manager/manager.hpp
+++ b/vpd-manager/manager.hpp
@@ -117,6 +117,11 @@
      */
     void deleteFRUVPD(const sdbusplus::message::object_path& path);
 
+    /** @brief Api to perform VPD collection for a single fru.
+     *  @param[in] path - Dbus object path of that fru.
+     */
+    void collectFRUVPD(const sdbusplus::message::object_path& path);
+
   private:
     /**
      * @brief An api to process some initial requirements.
@@ -158,6 +163,15 @@
     void restoreSystemVpd();
 
     /**
+     * @brief An api to trigger vpd collection for a fru by bind/unbind of
+     * driver.
+     * @param[in] singleFru - Json of a single fru inder a given EEPROM path.
+     * @param[in] path - Inventory path.
+     */
+    void triggerVpdCollection(const nlohmann::json& singleFru,
+                              const std::string& path);
+
+    /**
      * @brief Check for essential fru in the system.
      * The api check for the presence of FRUs marked as essential and logs PEL
      * in case they are missing.