PEL: LightPath: Assert LED groups

Fill in the functions to get the LED group D-Bus paths corresponding to
the called out location codes, and then set the Asserted property on
that path to turn on the LEDs.

If there are any problems looking up any of the LED groups, then do not
turn on any LEDs at all, even if others were OK.  In this case, the
system attention indicator will be turned on instead.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I7e3ee6259d972dd2c6939c5a1004c6d25c40e38a
diff --git a/extensions/openpower-pels/data_interface.cpp b/extensions/openpower-pels/data_interface.cpp
index c57593d..4cad337 100644
--- a/extensions/openpower-pels/data_interface.cpp
+++ b/extensions/openpower-pels/data_interface.cpp
@@ -31,6 +31,7 @@
 {
 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
 constexpr auto vpdManager = "com.ibm.VPD.Manager";
+constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager";
 } // namespace service_name
 
 namespace object_path
@@ -66,6 +67,8 @@
 constexpr auto invCompatible =
     "xyz.openbmc_project.Inventory.Decorator.Compatible";
 constexpr auto vpdManager = "com.ibm.VPD.Manager";
+constexpr auto association = "xyz.openbmc_project.Association";
+constexpr auto ledGroup = "xyz.openbmc_project.Led.Group";
 } // namespace interface
 
 using namespace sdbusplus::xyz::openbmc_project::State::OperatingSystem::server;
@@ -412,5 +415,34 @@
     return shortest;
 }
 
+std::string
+    DataInterface::getFaultLEDGroup(const std::string& inventoryPath) const
+{
+    auto associationPath = inventoryPath + "/" + "fault_led_group";
+    auto service = getService(associationPath, interface::association);
+
+    DBusValue endpoints;
+    getProperty(service, associationPath, interface::association, "endpoints",
+                endpoints);
+    auto paths = std::get<std::vector<std::string>>(endpoints);
+    if (paths.empty())
+    {
+        throw std::runtime_error("Association endpoints property empty");
+    }
+
+    return paths[0];
+}
+
+void DataInterface::assertLEDGroup(const std::string& ledGroup,
+                                   bool value) const
+{
+    DBusValue variant = value;
+    auto method =
+        _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(),
+                             interface::dbusProperty, "Set");
+    method.append(interface::ledGroup, "Asserted", variant);
+    _bus.call(method);
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index 2a80dba..09bcf59 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -281,6 +281,26 @@
                                                 uint16_t node,
                                                 bool expanded) const = 0;
 
+    /**
+     * @brief Returns the fault LED group D-Bus path for the inventory
+     *        D-Bus path passed in.
+     *
+     * @param[in] inventoryPath - The inventory D-Bus path
+     *
+     * @return std::string - The fault LED group D-Bus path
+     */
+    virtual std::string
+        getFaultLEDGroup(const std::string& inventoryPath) const = 0;
+
+    /**
+     * @brief Sets the Asserted property on the LED group passed in.
+     *
+     * @param[in] ledGroup - The LED group D-Bus path
+     * @param[in] value - The value to set it to
+     */
+    virtual void assertLEDGroup(const std::string& ledGroup,
+                                bool value) const = 0;
+
   protected:
     /**
      * @brief Sets the host on/off state and runs any
@@ -508,6 +528,25 @@
                                         uint16_t node,
                                         bool expanded) const override;
 
+    /**
+     * @brief Returns the fault LED group D-Bus path for the inventory
+     *        D-Bus path passed in.
+     *
+     * @param[in] inventoryPath - The inventory D-Bus path
+     *
+     * @return std::string - The fault LED group D-Bus path
+     */
+    std::string
+        getFaultLEDGroup(const std::string& inventoryPath) const override;
+
+    /**
+     * @brief Sets the Asserted property on the LED group passed in.
+     *
+     * @param[in] ledGroup - The LED group D-Bus path
+     * @param[in] value - The value to set it to
+     */
+    void assertLEDGroup(const std::string& ledGroup, bool value) const override;
+
   private:
     /**
      * @brief Reads the BMC firmware version string and puts it into
diff --git a/extensions/openpower-pels/service_indicators.cpp b/extensions/openpower-pels/service_indicators.cpp
index 074251c..3f934c7 100644
--- a/extensions/openpower-pels/service_indicators.cpp
+++ b/extensions/openpower-pels/service_indicators.cpp
@@ -15,6 +15,8 @@
  */
 #include "service_indicators.hpp"
 
+#include <fmt/format.h>
+
 #include <bitset>
 #include <phosphor-logging/log.hpp>
 
@@ -182,13 +184,62 @@
 std::vector<std::string> LightPath::getLEDGroupPaths(
     const std::vector<std::string>& locationCodes) const
 {
-    // TODO
-    return {};
+    std::vector<std::string> ledGroups;
+    std::string inventoryPath;
+
+    for (const auto& locCode : locationCodes)
+    {
+        try
+        {
+            inventoryPath =
+                _dataIface.getInventoryFromLocCode(locCode, 0, true);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>(fmt::format("Could not get inventory path for "
+                                        "location code {} ({}).",
+                                        locCode, e.what())
+                                .c_str());
+
+            // Unless we can get the LEDs for all FRUs, we can't turn
+            // on any of them, so clear the list and quit.
+            ledGroups.clear();
+            break;
+        }
+
+        try
+        {
+            ledGroups.push_back(_dataIface.getFaultLEDGroup(inventoryPath));
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>(fmt::format("Could not get LED group path for "
+                                        "inventory path {} ({}).",
+                                        inventoryPath, e.what())
+                                .c_str());
+            ledGroups.clear();
+            break;
+        }
+    }
+
+    return ledGroups;
 }
 
 void LightPath::assertLEDs(const std::vector<std::string>& ledGroups) const
 {
-    // TODO
+    for (const auto& ledGroup : ledGroups)
+    {
+        try
+        {
+            _dataIface.assertLEDGroup(ledGroup, true);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>(fmt::format("Failed to assert LED group {} ({})",
+                                        ledGroup, e.what())
+                                .c_str());
+        }
+    }
 }
 
 } // namespace openpower::pels::service_indicators
diff --git a/extensions/openpower-pels/service_indicators.hpp b/extensions/openpower-pels/service_indicators.hpp
index 292c30d..522f3db 100644
--- a/extensions/openpower-pels/service_indicators.hpp
+++ b/extensions/openpower-pels/service_indicators.hpp
@@ -123,13 +123,20 @@
 
   private:
     /**
-     * @brief Description TODO
+     * @brief Returns the LED group D-Bus paths to use to turn on the
+     *        LEDs for the passed in location codes.
+     *
+     * @param[in] locationCodes - The location codes
+     *
+     * @return std::vector<std::string> - The LED group D-Bus paths
      */
     std::vector<std::string>
         getLEDGroupPaths(const std::vector<std::string>& locationCodes) const;
 
     /**
-     * @brief Description TODO
+     * @brief Sets the Assert property on the LED group D-Bus objects
+     *
+     * @param[in] std::vector<std::string> - The LED group D-Bus paths
      */
     void assertLEDs(const std::vector<std::string>& ledGroups) const;
 
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
index 0e55ecf..e9d9fdf 100644
--- a/test/openpower-pels/mocks.hpp
+++ b/test/openpower-pels/mocks.hpp
@@ -41,6 +41,12 @@
     MOCK_METHOD(std::string, getInventoryFromLocCode,
                 (const std::string&, uint16_t, bool), (const override));
 
+    MOCK_METHOD(std::string, getFaultLEDGroup, (const std::string&),
+                (const override));
+
+    MOCK_METHOD(void, assertLEDGroup, (const std::string&, bool),
+                (const override));
+
     void changeHostState(bool newState)
     {
         setHostUp(newState);
diff --git a/test/openpower-pels/service_indicators_test.cpp b/test/openpower-pels/service_indicators_test.cpp
index c391e92..8f1d9ab 100644
--- a/test/openpower-pels/service_indicators_test.cpp
+++ b/test/openpower-pels/service_indicators_test.cpp
@@ -257,3 +257,85 @@
                   (std::vector<std::string>{}));
     }
 }
+
+// Test the activate() function
+TEST(ServiceIndicatorsTest, ActivateTest)
+{
+    // pelFactory() will create a PEL with 1 callout with location code
+    // U42.  Test the LED for that gets activated.
+    {
+        MockDataInterface dataIface;
+        service_indicators::LightPath lightPath{dataIface};
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("U42", 0, true))
+            .WillOnce(Return("/system/chassis/processor"));
+
+        EXPECT_CALL(dataIface, getFaultLEDGroup("/system/chassis/processor"))
+            .WillOnce(Return("/led/groups/cpu0"));
+
+        EXPECT_CALL(dataIface, assertLEDGroup("/led/groups/cpu0", true))
+            .Times(1);
+
+        auto data = pelFactory(1, 'O', 0x20, 0xA400, 500);
+        PEL pel{data};
+
+        lightPath.activate(pel);
+    }
+
+    // Make getInventoryFromLocCode fail
+    {
+        MockDataInterface dataIface;
+        service_indicators::LightPath lightPath{dataIface};
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("U42", 0, true))
+            .WillOnce(Throw(std::runtime_error("Fail")));
+
+        EXPECT_CALL(dataIface, getFaultLEDGroup(_)).Times(0);
+
+        EXPECT_CALL(dataIface, assertLEDGroup(_, true)).Times(0);
+
+        auto data = pelFactory(1, 'O', 0x20, 0xA400, 500);
+        PEL pel{data};
+
+        lightPath.activate(pel);
+    }
+
+    // Make getFaultLEDGroup fail
+    {
+        MockDataInterface dataIface;
+        service_indicators::LightPath lightPath{dataIface};
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("U42", 0, true))
+            .WillOnce(Return("/system/chassis/processor"));
+
+        EXPECT_CALL(dataIface, getFaultLEDGroup("/system/chassis/processor"))
+            .WillOnce(Throw(std::runtime_error("Fail")));
+
+        EXPECT_CALL(dataIface, assertLEDGroup(_, true)).Times(0);
+
+        auto data = pelFactory(1, 'O', 0x20, 0xA400, 500);
+        PEL pel{data};
+
+        lightPath.activate(pel);
+    }
+
+    // Make assertLEDGroup fail
+    {
+        MockDataInterface dataIface;
+        service_indicators::LightPath lightPath{dataIface};
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("U42", 0, true))
+            .WillOnce(Return("/system/chassis/processor"));
+
+        EXPECT_CALL(dataIface, getFaultLEDGroup("/system/chassis/processor"))
+            .WillOnce(Return("/led/groups/cpu0"));
+
+        EXPECT_CALL(dataIface, assertLEDGroup("/led/groups/cpu0", true))
+            .WillOnce(Throw(std::runtime_error("Fail")));
+
+        auto data = pelFactory(1, 'O', 0x20, 0xA400, 500);
+        PEL pel{data};
+
+        lightPath.activate(pel);
+    }
+}