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