led-use-json: Use specific config files

- When enabled `led-use-json`, it should use the JsonConfig class to
  find the JSON file name, and do not start until JSON config is
  found.

- If it has to wait for the IBMCompatible interface to show up on
  D-bus first, it will wait for that.

Tested:
- If there is a `/etc/phosphor-led-manager/led-group-config.json`
  path, congfile=/etc/phosphor-led-manager/led-group-config.json
- If there is a `/usr/share/phosphor-led-manager/led-group-config.json`
  path and there is not `/etc/phosphor-led-manager/`,
  congfile=/usr/share/phosphor-led-manager/led-group-config.json
- If the above two paths do not exist, get the `Name` property of the
  `xyz.openbmc_project.Configuration.IBMCompatibleSystem` interface,
  congfile=/usr/share/phosphor-led-manager/${Name}/led-group-config.json

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I1528a3d3711ef6b604868387ad42cb3c0afde119
diff --git a/Makefile.am b/Makefile.am
index 99b8e01..5128fda 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -19,10 +19,12 @@
 
 phosphor_ledmanager_LDFLAGS = $(SDBUSPLUS_LIBS) \
                               $(PHOSPHOR_LOGGING_LIBS) \
-                              $(PHOSPHOR_DBUS_INTERFACES_LIBS)
+                              $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+                              $(SDEVENTPLUS_LIBS)
 phosphor_ledmanager_CFLAGS =  $(SDBUSPLUS_CFLAGS) \
                               $(PHOSPHOR_LOGGING_CFLAGS) \
-                              $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+                              $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+                              $(SDEVENTPLUS_CFLAGS)
 
 led_default_configdir    	 = 	${datadir}/phosphor-led-manager
 led_ibm_rainier_2u_configdir = 	${datadir}/phosphor-led-manager/ibm,rainier-2u
diff --git a/configure.ac b/configure.ac
index 89feb08..4a48e57 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,6 +42,7 @@
 
 # Checks for modules
 PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],, [AC_MSG_ERROR([Could not find sdbusplus...openbmc/sdbusplus package required])])
+PKG_CHECK_MODULES([SDEVENTPLUS], [sdeventplus],, [AC_MSG_ERROR([Could not find sdeventplus...openbmc/sdeventplus package required])])
 PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],, [AC_MSG_ERROR([Could not find phosphor-logging...openbmc/phosphor-logging package required])])
 PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],, [AC_MSG_ERROR([Could not find phosphor-dbus-interfaces...openbmc/phosphor-dbus-interfaces package required])])
 AC_CHECK_HEADER(nlohmann/json.hpp, ,[AC_MSG_ERROR([Could not find nlohmann/json.hpp...nlohmann/json package required])])
diff --git a/json-config.hpp b/json-config.hpp
index e0448c5..462a84e 100644
--- a/json-config.hpp
+++ b/json-config.hpp
@@ -1,147 +1,229 @@
-#include "config.h"
+#include "utils.hpp"
 
-#include "ledlayout.hpp"
-
-#include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdeventplus/event.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>;
+using namespace phosphor::logging;
 
-// Priority for a particular LED needs to stay SAME across all groups
-// phosphor::led::Layout::Action can only be one of `Blink` and `On`
-using PriorityMap = std::map<std::string, phosphor::led::Layout::Action>;
-
-/** @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)
+namespace phosphor
 {
-    using namespace phosphor::logging;
+namespace led
+{
 
-    if (!fs::exists(path) || fs::is_empty(path))
+static constexpr auto confFileName = "led-group-config.json";
+static constexpr auto confOverridePath = "/etc/phosphor-led-manager";
+static constexpr auto confBasePath = "/usr/share/phosphor-led-manager";
+static constexpr auto confCompatibleInterface =
+    "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
+static constexpr auto confCompatibleProperty = "Names";
+
+class JsonConfig
+{
+  public:
+    /**
+     * @brief Constructor
+     *
+     * Looks for the JSON config file.  If it can't find one, then it
+     * will watch entity-manager for the IBMCompatibleSystem interface
+     * to show up.
+     *
+     * @param[in] bus       - The D-Bus object
+     * @param[in] event     - sd event handler
+     */
+    JsonConfig(sdbusplus::bus::bus& bus, sdeventplus::Event& event) :
+        event(event)
     {
-        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");
+        match = std::make_unique<sdbusplus::server::match::match>(
+            bus,
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::sender(
+                    "xyz.openbmc_project.EntityManager"),
+            std::bind(&JsonConfig::ifacesAddedCallback, this,
+                      std::placeholders::_1));
+        getFilePath();
+
+        if (!confFile.empty())
+        {
+            match.reset();
+        }
     }
 
-    try
+    /**
+     * @brief Get the configuration file
+     *
+     * @return filesystem path
+     */
+    inline const fs::path& getConfFile() const
     {
-        std::ifstream jsonFile(path);
-        return Json::parse(jsonFile);
+        return confFile;
     }
-    catch (const std::exception& e)
+
+  private:
+    /** @brief Check the file path exists
+     *
+     *  @param[in]  names   -  Vector of the confCompatible Property
+     *
+     *  @return             -  True or False
+     */
+    bool filePathExists(const std::vector<std::string>& names)
     {
-        log<level::ERR>("Failed to parse config file",
+        auto it =
+            std::find_if(names.begin(), names.end(), [this](const auto& name) {
+                auto tempConfFile =
+                    fs::path{confBasePath} / name / confFileName;
+                if (fs::exists(tempConfFile))
+                {
+                    confFile = tempConfFile;
+                    return true;
+                }
+                return false;
+            });
+        return it == names.end() ? false : true;
+    }
+
+    /**
+     * @brief The interfacesAdded callback function that looks for
+     *        the IBMCompatibleSystem interface.  If it finds it,
+     *        it uses the Names property in the interface to find
+     *        the JSON config file to use.
+     *
+     * @param[in] msg - The D-Bus message contents
+     */
+    void ifacesAddedCallback(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        std::map<std::string,
+                 std::map<std::string, std::variant<std::vector<std::string>>>>
+            interfaces;
+
+        msg.read(path, interfaces);
+
+        if (interfaces.find(confCompatibleInterface) == interfaces.end())
+        {
+            return;
+        }
+
+        // Get the "Name" property value of the
+        // "xyz.openbmc_project.Configuration.IBMCompatibleSystem" interface
+        const auto& properties = interfaces.at(confCompatibleInterface);
+
+        if (properties.find(confCompatibleProperty) == properties.end())
+        {
+            return;
+        }
+        auto names = std::get<std::vector<std::string>>(
+            properties.at(confCompatibleProperty));
+
+        if (filePathExists(names))
+        {
+            match.reset();
+
+            // This results in event.loop() exiting in getSystemLedMap
+            event.exit(0);
+        }
+    }
+
+    /**
+     * Get the json configuration file. The first location found to contain the
+     * json config file from the following locations in order.
+     * confOverridePath: /etc/phosphor-led-manager/led-group-config.json
+     * confBasePath: /usr/shard/phosphor-led-manager/led-group-config.json
+     * the name property of the confCompatibleInterface:
+     * /usr/shard/phosphor-led-manager/${Name}/led-group-config.json
+     *
+     * @brief Get the configuration file to be used
+     *
+     * @return
+     */
+    const void getFilePath()
+    {
+        // Check override location
+        confFile = fs::path{confOverridePath} / confFileName;
+        if (fs::exists(confFile))
+        {
+            return;
+        }
+
+        // If the default file is there, use it
+        confFile = fs::path{confBasePath} / confFileName;
+        if (fs::exists(confFile))
+        {
+            return;
+        }
+        confFile.clear();
+
+        try
+        {
+            // Get all objects implementing the compatible interface
+            auto objects =
+                dBusHandler.getSubTreePaths("/", confCompatibleInterface);
+            for (const auto& path : objects)
+            {
+                try
+                {
+                    // Retrieve json config compatible relative path locations
+                    auto value = dBusHandler.getProperty(
+                        path, confCompatibleInterface, confCompatibleProperty);
+
+                    auto confCompatValues =
+                        std::get<std::vector<std::string>>(value);
+
+                    // Look for a config file at each name relative to the base
+                    // path and use the first one found
+                    if (filePathExists(confCompatValues))
+                    {
+                        // Use the first config file found at a listed location
+                        break;
+                    }
+                    confFile.clear();
+                }
+                catch (const sdbusplus::exception::SdBusError& e)
+                {
+                    // Property unavailable on object.
+                    log<level::ERR>(
+                        "Failed to get Names property",
                         entry("ERROR=%s", e.what()),
-                        entry("FILE_PATH=%s", path.c_str()));
-        throw std::runtime_error("Failed to parse config file");
-    }
-}
+                        entry("INTERFACE=%s", confCompatibleInterface),
+                        entry("PATH=%s", path.c_str()));
 
-/** @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 Validate the Priority of an LED is same across ALL groups
- *
- *  @param[in] name - led name member of each group
- *  @param[in] priority - member priority of each group
- *  @param[out] priorityMap - std::map, key:name, value:priority
- *
- *  @return
- */
-void validatePriority(const std::string& name,
-                      const phosphor::led::Layout::Action& priority,
-                      PriorityMap& priorityMap)
-{
-    using namespace phosphor::logging;
-
-    auto iter = priorityMap.find(name);
-    if (iter == priorityMap.end())
-    {
-        priorityMap.emplace(name, priority);
+                    confFile.clear();
+                }
+            }
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            log<level::ERR>("Failed to call the SubTreePaths method",
+                            entry("ERROR=%s", e.what()),
+                            entry("INTERFACE=%s", confCompatibleInterface));
+        }
         return;
     }
 
-    if (iter->second != priority)
-    {
-        log<level::ERR>("Priority of LED is not same across all",
-                        entry("Name=%s", name.c_str()),
-                        entry(" Old Priority=%d", iter->second),
-                        entry(" New priority=%d", priority));
+  private:
+    /**
+     * @brief sd event handler.
+     */
+    sdeventplus::Event& event;
 
-        throw std::runtime_error(
-            "Priority of at least one LED is not same across groups");
-    }
-}
+    /**
+     * @brief The JSON config file
+     */
+    fs::path confFile;
 
-/** @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{};
-    PriorityMap priorityMap{};
+    /**
+     * @brief The interfacesAdded match that is used to wait
+     *        for the IBMCompatibleSystem interface to show up.
+     */
+    std::unique_ptr<sdbusplus::server::match::match> match;
 
-    // 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"));
-
-            // Same LEDs can be part of multiple groups. However, their
-            // priorities across groups need to match.
-            validatePriority(name, priority, priorityMap);
-
-            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;
-}
+    /** DBusHandler class handles the D-Bus operations */
+    utils::DBusHandler dBusHandler;
+};
+} // namespace led
+} // namespace phosphor
diff --git a/json-parser.hpp b/json-parser.hpp
new file mode 100644
index 0000000..245587d
--- /dev/null
+++ b/json-parser.hpp
@@ -0,0 +1,179 @@
+#include "config.h"
+
+#include "json-config.hpp"
+#include "ledlayout.hpp"
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.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>;
+
+// Priority for a particular LED needs to stay SAME across all groups
+// phosphor::led::Layout::Action can only be one of `Blink` and `On`
+using PriorityMap = std::map<std::string, phosphor::led::Layout::Action>;
+
+/** @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 Validate the Priority of an LED is same across ALL groups
+ *
+ *  @param[in] name - led name member of each group
+ *  @param[in] priority - member priority of each group
+ *  @param[out] priorityMap - std::map, key:name, value:priority
+ *
+ *  @return
+ */
+void validatePriority(const std::string& name,
+                      const phosphor::led::Layout::Action& priority,
+                      PriorityMap& priorityMap)
+{
+    using namespace phosphor::logging;
+
+    auto iter = priorityMap.find(name);
+    if (iter == priorityMap.end())
+    {
+        priorityMap.emplace(name, priority);
+        return;
+    }
+
+    if (iter->second != priority)
+    {
+        log<level::ERR>("Priority of LED is not same across all",
+                        entry("Name=%s", name.c_str()),
+                        entry(" Old Priority=%d", iter->second),
+                        entry(" New priority=%d", priority));
+
+        throw std::runtime_error(
+            "Priority of at least one LED is not same across groups");
+    }
+}
+
+/** @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{};
+    PriorityMap priorityMap{};
+
+    // 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"));
+
+            // Same LEDs can be part of multiple groups. However, their
+            // priorities across groups need to match.
+            validatePriority(name, priority, priorityMap);
+
+            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;
+}
+
+/** @brief Get led map from LED groups JSON config
+ *
+ *  @return LedMap - Generated an std::map of LedAction
+ */
+const LedMap getSystemLedMap()
+{
+    // Get a new Dbus
+    auto bus = sdbusplus::bus::new_bus();
+
+    // Get a new event loop
+    auto event = sdeventplus::Event::get_new();
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_IMPORTANT);
+    phosphor::led::JsonConfig jsonConfig(bus, event);
+
+    // The event loop will be terminated from inside of a function in JsonConfig
+    // after finding the configuration file
+    if (jsonConfig.getConfFile().empty())
+    {
+        event.loop();
+    }
+
+    // Detach the bus from its sd_event event loop object
+    bus.detach_event();
+
+    return loadJsonConfig(jsonConfig.getConfFile());
+}
diff --git a/led-main.cpp b/led-main.cpp
index 7dc0488..8dcfff6 100644
--- a/led-main.cpp
+++ b/led-main.cpp
@@ -2,7 +2,7 @@
 
 #include "group.hpp"
 #ifdef LED_USE_JSON
-#include "json-config.hpp"
+#include "json-parser.hpp"
 #else
 #include "led-gen.hpp"
 #endif
@@ -19,7 +19,7 @@
     auto& bus = phosphor::led::utils::DBusHandler::getBus();
 
 #ifdef LED_USE_JSON
-    auto systemLedMap = loadJsonConfig(LED_JSON_FILE);
+    auto systemLedMap = getSystemLedMap();
 #endif
 
     /** @brief Group manager object */
diff --git a/test/Makefile.am b/test/Makefile.am
index 0a6ef3a..7c3dbc7 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -7,6 +7,6 @@
 check_PROGRAMS = utest
 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_LDFLAGS = -lgtest_main -lgtest $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS) $(SYSTEMD_LIBS) $(PHOSPHOR_LOGGING_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) $(SDEVENTPLUS_LIBS)
 utest_SOURCES = utest.cpp utest-led-json.cpp utest-serialize.cpp
 utest_LDADD = $(top_builddir)/manager.o $(top_builddir)/serialize.o $(top_builddir)/utils.o
diff --git a/test/utest-led-json.cpp b/test/utest-led-json.cpp
index f0ca022..a8c7398 100644
--- a/test/utest-led-json.cpp
+++ b/test/utest-led-json.cpp
@@ -1,4 +1,4 @@
-#include "json-config.hpp"
+#include "json-parser.hpp"
 
 #include <gtest/gtest.h>
 
diff --git a/utils.cpp b/utils.cpp
index c9a21e5..6c122f9 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -92,6 +92,26 @@
     bus.call_noreply(method);
 }
 
+const std::vector<std::string>
+    DBusHandler::getSubTreePaths(const std::string& objectPath,
+                                 const std::string& interface)
+{
+    std::vector<std::string> paths;
+
+    auto& bus = DBusHandler::getBus();
+
+    auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_OBJ_PATH,
+                                      MAPPER_IFACE, "GetSubTreePaths");
+    method.append(objectPath.c_str());
+    method.append(0); // Depth 0 to search all
+    method.append(std::vector<std::string>({interface.c_str()}));
+    auto reply = bus.call(method);
+
+    reply.read(paths);
+
+    return paths;
+}
+
 } // namespace utils
 } // namespace led
 } // namespace phosphor
\ No newline at end of file
diff --git a/utils.hpp b/utils.hpp
index 68a616a..ef56f5f 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -76,6 +76,17 @@
                      const std::string& interface,
                      const std::string& propertyName,
                      const PropertyValue& value) const;
+
+    /** @brief Get sub tree paths by the path and interface of the DBus.
+     *
+     *  @param[in]  objectPath   -  D-Bus object path
+     *  @param[in]  interface    -  D-Bus object interface
+     *
+     *  @return std::vector<std::string> vector of subtree paths
+     */
+    const std::vector<std::string>
+        getSubTreePaths(const std::string& objectPath,
+                        const std::string& interface);
 };
 
 } // namespace utils