Update led-manager to use JSON

Current LED manager uses compile time generated led-gen.hpp and creates
DBus objects for the groups.
We would need this changed to use the generated JSON.

Tested: JSON used at runtime when --enable-json given at configure time.
led-gen.hpp not created when using JSON and led-gen.hpp created by
default when using YAML at build time.

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I781f8cb090ece8b87730e6c97795624282857c64
diff --git a/Makefile.am b/Makefile.am
index ac8f6c7..2660b32 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,11 +6,13 @@
                 manager.cpp \
                 group.cpp
 
+if !LED_USE_JSON
 BUILT_SOURCES = led-gen.hpp
 CLEANFILES = led-gen.hpp
 
 led-gen.hpp: ${srcdir}/parse_led.py
 	$(AM_V)@LEDGEN@ > $@
+endif
 
 phosphor_ledmanager_LDFLAGS = $(SDBUSPLUS_LIBS) \
                               $(PHOSPHOR_LOGGING_LIBS) \
diff --git a/configure.ac b/configure.ac
index 9529fe9..988363e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -65,6 +65,17 @@
 LEDGEN="$PYTHON $srcdir/parse_led.py -i $YAML_PATH"
 AC_SUBST(LEDGEN)
 
+# JSON configuration file
+AC_ARG_VAR(LED_JSON_FILE, [The LED configuration JSON file])
+AS_IF([test "x$LED_JSON_FILE" == "x"], [LED_JSON_FILE="/usr/share/phosphor-led-manager/led-group-config.json"])
+AC_DEFINE_UNQUOTED([LED_JSON_FILE], ["$LED_JSON_FILE"], [The LED configuration JSON file])
+
+# enable JSON configuration
+AC_ARG_ENABLE([use_json],
+    AS_HELP_STRING([--enable-use_json], [Enable JSON configuration.]))
+AC_DEFINE([LED_USE_JSON],[],[Enable JSON configuration.])
+AM_CONDITIONAL([LED_USE_JSON], [test "x$enable-use_json" == "xyes"])
+
 AC_DEFINE(CALLOUT_FWD_ASSOCIATION, "callout", [The name of the callout's forward association.])
 AC_DEFINE(CALLOUT_REV_ASSOCIATION, "fault", [The name of the callout's reverse association.])
 AC_DEFINE(ELOG_ENTRY, "entry", [Path element indicates an error log entry under logging namespace.])
diff --git a/json-config.hpp b/json-config.hpp
new file mode 100644
index 0000000..3d1caec
--- /dev/null
+++ b/json-config.hpp
@@ -0,0 +1,104 @@
+#include "config.h"
+
+#include "ledlayout.hpp"
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+
+namespace fs = std::filesystem;
+
+using Json = nlohmann::json;
+using LedAction = std::set<phosphor::led::Layout::LedAction>;
+using LedMap = std::map<std::string, LedAction>;
+
+/** @brief Parse LED JSON file and output Json object
+ *
+ *  @param[in] path - path of LED JSON file
+ *
+ *  @return const Json - Json object
+ */
+const Json readJson(const fs::path& path)
+{
+    using namespace phosphor::logging;
+
+    if (!fs::exists(path) || fs::is_empty(path))
+    {
+        log<level::ERR>("Incorrect File Path or empty file",
+                        entry("FILE_PATH=%s", path.c_str()));
+        throw std::runtime_error("Incorrect File Path or empty file");
+    }
+
+    try
+    {
+        std::ifstream jsonFile(path);
+        return Json::parse(jsonFile);
+    }
+    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()));
+        throw std::runtime_error("Failed to parse config file");
+    }
+}
+
+/** @brief Returns action enum based on string
+ *
+ *  @param[in] action - action string
+ *
+ *  @return Action - action enum (On/Blink)
+ */
+phosphor::led::Layout::Action getAction(const std::string& action)
+{
+    assert(action == "On" || action == "Blink");
+
+    return action == "Blink" ? phosphor::led::Layout::Blink
+                             : phosphor::led::Layout::On;
+}
+
+/** @brief Load JSON config and return led map
+ *
+ *  @return LedMap - Generated an std::map of LedAction
+ */
+const LedMap loadJsonConfig(const fs::path& path)
+{
+    LedMap ledMap{};
+
+    // define the default JSON as empty
+    const Json empty{};
+    auto json = readJson(path);
+
+    auto leds = json.value("leds", empty);
+    for (const auto& entry : leds)
+    {
+        fs::path tmpPath(std::string{OBJPATH});
+        tmpPath /= entry.value("group", "");
+        auto objpath = tmpPath.string();
+        auto members = entry.value("members", empty);
+
+        LedAction ledActions{};
+        for (const auto& member : members)
+        {
+            auto name = member.value("name", "");
+            auto action = getAction(member.value("Action", ""));
+            uint8_t dutyOn = member.value("DutyOn", 50);
+            uint16_t period = member.value("Period", 0);
+
+            // Since only have Blink/On and default priority is Blink
+            auto priority = getAction(member.value("Priority", "Blink"));
+            phosphor::led::Layout::LedAction ledAction{name, action, dutyOn,
+                                                       period, priority};
+            ledActions.emplace(ledAction);
+        }
+
+        // Generated an std::map of LedGroupNames to std::set of LEDs
+        // containing the name and properties.
+        ledMap.emplace(objpath, ledActions);
+    }
+
+    return ledMap;
+}
diff --git a/led-main.cpp b/led-main.cpp
index cc0e6cf..53e3a71 100644
--- a/led-main.cpp
+++ b/led-main.cpp
@@ -1,7 +1,11 @@
 #include "config.h"
 
 #include "group.hpp"
+#ifdef LED_USE_JSON
+#include "json-config.hpp"
+#else
 #include "led-gen.hpp"
+#endif
 #include "ledlayout.hpp"
 #include "manager.hpp"
 
@@ -12,6 +16,10 @@
     /** @brief Dbus constructs used by LED Group manager */
     sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
 
+#ifdef LED_USE_JSON
+    auto systemLedMap = loadJsonConfig(LED_JSON_FILE);
+#endif
+
     /** @brief Group manager object */
     phosphor::led::Manager manager(bus, systemLedMap);
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 0bf5215..d4cd3bd 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,5 +8,5 @@
 utest_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
 utest_CXXFLAGS = $(PTHREAD_CFLAGS)
 utest_LDFLAGS = -lgtest_main -lgtest $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS) $(SYSTEMD_LIBS) $(PHOSPHOR_LOGGING_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS)
-utest_SOURCES = utest.cpp
+utest_SOURCES = utest.cpp utest-led-json.cpp
 utest_LDADD = $(top_builddir)/manager.o
diff --git a/test/config/led-group-config-malformed.json b/test/config/led-group-config-malformed.json
new file mode 100644
index 0000000..4281cdf
--- /dev/null
+++ b/test/config/led-group-config-malformed.json
@@ -0,0 +1,40 @@
+{
+    "leds": [
+        {
+            "group": "bmc_booted"
+            "members": [
+                {
+                    "name": "heartbeat",
+                    "Action": "On"
+                }
+            ]
+        },
+        {
+            "group": "power_on",
+            "members": [
+                {
+                    "name": "power",
+                    "Action": "On",
+                    "Priority": "On"
+                }
+            ]
+        },
+        {
+            "group": "enclosure_identify",
+            "members": [
+                {
+                    "name": "front_id",
+                    "Action": "Blink",
+                    "DutyOn": 50,
+                    "Period": 1000
+                },
+                {
+                    "name": "rear_id",
+                    "Action": "Blink",
+                    "DutyOn": 50,
+                    "Period": 1000
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/config/led-group-config.json b/test/config/led-group-config.json
new file mode 100644
index 0000000..aff1fd4
--- /dev/null
+++ b/test/config/led-group-config.json
@@ -0,0 +1,40 @@
+{
+    "leds": [
+        {
+            "group": "bmc_booted",
+            "members": [
+                {
+                    "name": "heartbeat",
+                    "Action": "On"
+                }
+            ]
+        },
+        {
+            "group": "power_on",
+            "members": [
+                {
+                    "name": "power",
+                    "Action": "On",
+                    "Priority": "On"
+                }
+            ]
+        },
+        {
+            "group": "enclosure_identify",
+            "members": [
+                {
+                    "name": "front_id",
+                    "Action": "Blink",
+                    "DutyOn": 50,
+                    "Period": 1000
+                },
+                {
+                    "name": "rear_id",
+                    "Action": "Blink",
+                    "DutyOn": 50,
+                    "Period": 1000
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/utest-led-json.cpp b/test/utest-led-json.cpp
new file mode 100644
index 0000000..32d89a8
--- /dev/null
+++ b/test/utest-led-json.cpp
@@ -0,0 +1,68 @@
+#include "json-config.hpp"
+
+#include <gtest/gtest.h>
+
+TEST(loadJsonConfig, testGoodPath)
+{
+    static constexpr auto jsonPath = "config/led-group-config.json";
+    LedMap ledMap = loadJsonConfig(jsonPath);
+
+    std::string objPath = "/xyz/openbmc_project/led/groups";
+    std::string bmcBooted = objPath + "/bmc_booted";
+    std::string powerOn = objPath + "/power_on";
+    std::string enclosureIdentify = objPath + "/enclosure_identify";
+
+    ASSERT_NE(ledMap.find(bmcBooted), ledMap.end());
+    ASSERT_NE(ledMap.find(powerOn), ledMap.end());
+    ASSERT_NE(ledMap.find(enclosureIdentify), ledMap.end());
+
+    LedAction bmcBootedActions = ledMap.at(bmcBooted);
+    LedAction powerOnActions = ledMap.at(powerOn);
+    LedAction enclosureIdentifyActions = ledMap.at(enclosureIdentify);
+
+    for (const auto& group : bmcBootedActions)
+    {
+        ASSERT_EQ(group.name, "heartbeat");
+        ASSERT_EQ(group.action, phosphor::led::Layout::On);
+        ASSERT_EQ(group.dutyOn, 50);
+        ASSERT_EQ(group.period, 0);
+        ASSERT_EQ(group.priority, phosphor::led::Layout::Blink);
+    }
+
+    for (const auto& group : powerOnActions)
+    {
+        ASSERT_EQ(group.name, "power");
+        ASSERT_EQ(group.action, phosphor::led::Layout::On);
+        ASSERT_EQ(group.dutyOn, 50);
+        ASSERT_EQ(group.period, 0);
+        ASSERT_EQ(group.priority, phosphor::led::Layout::On);
+    }
+
+    for (const auto& group : enclosureIdentifyActions)
+    {
+        if (group.name == "front_id")
+        {
+            ASSERT_EQ(group.action, phosphor::led::Layout::Blink);
+            ASSERT_EQ(group.dutyOn, 50);
+            ASSERT_EQ(group.period, 1000);
+            ASSERT_EQ(group.priority, phosphor::led::Layout::Blink);
+        }
+        else if (group.name == "rear_id")
+        {
+            ASSERT_EQ(group.action, phosphor::led::Layout::Blink);
+            ASSERT_EQ(group.dutyOn, 50);
+            ASSERT_EQ(group.period, 1000);
+            ASSERT_EQ(group.priority, phosphor::led::Layout::Blink);
+        }
+        else
+        {
+            ASSERT_TRUE(false);
+        }
+    }
+}
+
+TEST(loadJsonConfig, testBadPath)
+{
+    static constexpr auto jsonPath = "config/led-group-config-malformed.json";
+    ASSERT_THROW(loadJsonConfig(jsonPath), std::exception);
+}
\ No newline at end of file