lamp test: Add vritual lamp test function

- This feature adds the functionality that enables force updating
  certain physical LEDs and skip updating certain physical LEDs as
  part of lamp test.

- This is a generic feature that gives more control over handing
  physical LEDs.

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I6f06ed103b1ceefdaa774b7a74a4a392609000ed
diff --git a/configure.ac b/configure.ac
index 27d4d9d..4a50e38 100644
--- a/configure.ac
+++ b/configure.ac
@@ -101,6 +101,9 @@
     # lamp test timeout secs
     AC_ARG_VAR(LAMP_TEST_TIMEOUT_IN_SECS, [The lamp test timeout in seconds])
 
+    # JSON file having LED names that will be force updated and/or skipped during lamp test
+    AC_ARG_VAR(LAMP_TEST_LED_OVERRIDES_JSON, [JSON file having LED names that will be force updated and/or skipped during lamp test])
+
     AS_IF([test "x$LAMP_TEST_OBJECT" == "x"], [LAMP_TEST_OBJECT="/xyz/openbmc_project/led/groups/lamp_test"])
     AC_DEFINE_UNQUOTED([LAMP_TEST_OBJECT], ["$LAMP_TEST_OBJECT"], [The lamp test D-Bus object])
 
@@ -109,6 +112,9 @@
 
     AS_IF([test "x$LAMP_TEST_TIMEOUT_IN_SECS" == "x"], [LAMP_TEST_TIMEOUT_IN_SECS=240])
     AC_DEFINE_UNQUOTED([LAMP_TEST_TIMEOUT_IN_SECS], [$LAMP_TEST_TIMEOUT_IN_SECS], [The lamp test timeout in seconds])
+
+    AS_IF([test "x$LAMP_TEST_LED_OVERRIDES_JSON" == "x"], [LAMP_TEST_LED_OVERRIDES_JSON="/usr/share/phosphor-led-manager/lamp-test-led-overrides.json"])
+    AC_DEFINE_UNQUOTED([LAMP_TEST_LED_OVERRIDES_JSON], ["$LAMP_TEST_LED_OVERRIDES_JSON"], [JSON file having LED names that will be force updated and/or skipped during lamp test])
 )
 
 # Path of file for storing the names of asserted groups
diff --git a/lamptest.cpp b/lamptest.cpp
index aaf73de..8220f96 100644
--- a/lamptest.cpp
+++ b/lamptest.cpp
@@ -1,5 +1,3 @@
-#include "config.h"
-
 #include "lamptest.hpp"
 
 #include <phosphor-logging/log.hpp>
@@ -10,6 +8,7 @@
 {
 
 using namespace phosphor::logging;
+using Json = nlohmann::json;
 
 bool LampTest::processLEDUpdates(const Manager::group& ledsAssert,
                                  const Manager::group& ledsDeAssert)
@@ -19,6 +18,34 @@
     // stopped.
     if (isLampTestRunning)
     {
+        // Physical LEDs will be updated during lamp test
+        for (const auto& it : ledsDeAssert)
+        {
+            std::string path = std::string(PHY_LED_PATH) + it.name;
+            auto iter = std::find_if(
+                forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
+                [&path](const auto& name) { return name == path; });
+
+            if (iter != forceUpdateLEDs.end())
+            {
+                manager.drivePhysicalLED(path, Layout::Action::Off, it.dutyOn,
+                                         it.period);
+            }
+        }
+
+        for (const auto& it : ledsAssert)
+        {
+            std::string path = std::string(PHY_LED_PATH) + it.name;
+            auto iter = std::find_if(
+                forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
+                [&path](const auto& name) { return name == path; });
+
+            if (iter != forceUpdateLEDs.end())
+            {
+                manager.drivePhysicalLED(path, it.action, it.dutyOn, it.period);
+            }
+        }
+
         updatedLEDsDuringLampTest.emplace(
             std::make_pair(ledsAssert, ledsDeAssert));
         return true;
@@ -41,6 +68,16 @@
     // Set all the Physical action to Off
     for (const auto& path : physicalLEDPaths)
     {
+        auto iter =
+            std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
+                         [&path](const auto& skip) { return skip == path; });
+
+        if (iter != skipUpdateLEDs.end())
+        {
+            // Skip update physical path
+            continue;
+        }
+
         manager.drivePhysicalLED(path, Layout::Action::Off, 0, 0);
     }
 
@@ -70,6 +107,16 @@
 
     for (const auto& path : physicalLEDPaths)
     {
+        auto iter = std::find_if(
+            skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
+            [&path](const auto& skipLed) { return skipLed == path; });
+
+        if (iter != skipUpdateLEDs.end())
+        {
+            // Physical LEDs will be skipped
+            continue;
+        }
+
         // Reverse intercept path, Get the name of each member of physical led
         // e.g: path = /xyz/openbmc_project/led/physical/front_fan
         //      name = front_fan
@@ -137,6 +184,16 @@
     // Set all the Physical action to On for lamp test
     for (const auto& path : physicalLEDPaths)
     {
+        auto iter =
+            std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
+                         [&path](const auto& skip) { return skip == path; });
+
+        if (iter != skipUpdateLEDs.end())
+        {
+            // Skip update physical path
+            continue;
+        }
+
         manager.drivePhysicalLED(path, Layout::Action::On, 0, 0);
     }
 }
@@ -173,8 +230,8 @@
 void LampTest::restorePhysicalLedStates()
 {
     // restore physical LEDs states before lamp test
-    Manager::group savedLEDStatesDeAssert{};
-    manager.driveLEDs(physicalLEDStatesPriorToLampTest, savedLEDStatesDeAssert);
+    Manager::group ledsDeAssert{};
+    manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert);
     physicalLEDStatesPriorToLampTest.clear();
 
     // restore physical LEDs states during lamp test
@@ -203,5 +260,42 @@
     }
 }
 
+void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path)
+{
+    if (!fs::exists(path) || fs::is_empty(path))
+    {
+        log<level::INFO>("The file does not exist or is empty",
+                         entry("FILE_PATH=%s", path.c_str()));
+        return;
+    }
+
+    try
+    {
+        std::ifstream jsonFile(path);
+        auto json = Json::parse(jsonFile);
+
+        // define the default JSON as empty
+        const std::vector<std::string> empty{};
+        auto forceLEDs = json.value("forceLEDs", empty);
+        for (auto& member : forceLEDs)
+        {
+            forceUpdateLEDs.push_back(PHY_LED_PATH + member);
+        }
+
+        auto skipLEDs = json.value("skipLEDs", empty);
+        for (auto& member : skipLEDs)
+        {
+            skipUpdateLEDs.push_back(PHY_LED_PATH + member);
+        }
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to parse config file",
+                        entry("ERROR=%s", e.what()),
+                        entry("FILE_PATH=%s", path.c_str()));
+    }
+    return;
+}
+
 } // namespace led
 } // namespace phosphor
diff --git a/lamptest.hpp b/lamptest.hpp
index 1797dce..d69667a 100644
--- a/lamptest.hpp
+++ b/lamptest.hpp
@@ -1,8 +1,11 @@
 #pragma once
 
+#include "config.h"
+
 #include "group.hpp"
 #include "manager.hpp"
 
+#include <nlohmann/json.hpp>
 #include <sdeventplus/utility/timer.hpp>
 
 #include <queue>
@@ -37,7 +40,11 @@
     LampTest(const sdeventplus::Event& event, Manager& manager) :
         timer(event, std::bind(std::mem_fn(&LampTest::timeOutHandler), this)),
         manager(manager), groupObj(NULL)
-    {}
+    {
+        // Get the force update and/or skipped physical LEDs names from the
+        // lamp-test-led-overrides.json file during lamp
+        getPhysicalLEDNamesFromJson(LAMP_TEST_LED_OVERRIDES_JSON);
+    }
 
     /** @brief the lamp test request handler
      *
@@ -86,6 +93,14 @@
     /** @brief Physical LED states prior to lamp test */
     Manager::group physicalLEDStatesPriorToLampTest;
 
+    /** @brief Vector of names of physical LEDs, whose changes will be forcibly
+     *         updated even during lamp test. */
+    std::vector<std::string> forceUpdateLEDs;
+
+    /** @brief Vector of names of physical LEDs, that will be exempted from lamp
+     *         test */
+    std::vector<std::string> skipUpdateLEDs;
+
     /** @brief Start and restart lamp test depending on what is the current
      *         state. */
     void start();
@@ -116,6 +131,14 @@
      *  @param[in]  value   -  the Asserted property value
      */
     void doHostLampTest(bool value);
+
+    /** @brief Get physical LED names from lamp test JSON config file
+     *
+     *  @param[in]  path - path of LED JSON file
+     *
+     *  return
+     */
+    void getPhysicalLEDNamesFromJson(const fs::path& path);
 };
 
 } // namespace led
diff --git a/manager.cpp b/manager.cpp
index 6219091..eccbc36 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -120,7 +120,6 @@
         return;
     }
 #endif
-
     // This order of LED operation is important.
     if (ledsDeAssert.size())
     {