PEL: Set critical association to object paths

Create critical association to inventory d-bus objects.
This is being done for the items in callouts that need
their service indicators turned on in its code, and
that the other association endpoint is the chasis so
it can be used for health rollup.

The associations property on the
xyz.openbmc_project.Association.Definitions interface
will have following entry added to called out object path:
["health_rollup", "critical",
"/xyz/openbmc_project/inventory/system/chassis"]

Signed-off-by: Sumit Kumar <sumit_kumar@in.ibm.com>
Change-Id: I50dfe4807ac9c19f54c49dfa2b9ec7119aaffb96
diff --git a/extensions/openpower-pels/data_interface.cpp b/extensions/openpower-pels/data_interface.cpp
index 89b1f54..0365b8b 100644
--- a/extensions/openpower-pels/data_interface.cpp
+++ b/extensions/openpower-pels/data_interface.cpp
@@ -79,6 +79,7 @@
 constexpr auto operationalStatus =
     "xyz.openbmc_project.State.Decorator.OperationalStatus";
 constexpr auto logSetting = "xyz.openbmc_project.Logging.Settings";
+constexpr auto association = "xyz.openbmc_project.Association.Definitions";
 } // namespace interface
 
 using namespace sdbusplus::xyz::openbmc_project::State::Boot::server;
@@ -528,6 +529,39 @@
     _bus.call(method);
 }
 
+using AssociationTuple = std::tuple<std::string, std::string, std::string>;
+using AssociationsProperty = std::vector<AssociationTuple>;
+
+void DataInterface::setCriticalAssociation(const std::string& objectPath) const
+{
+    DBusValue getAssociationValue;
+
+    auto service = getService(objectPath, interface::association);
+
+    getProperty(service, objectPath, interface::association, "Associations",
+                getAssociationValue);
+
+    auto association = std::get<AssociationsProperty>(getAssociationValue);
+
+    AssociationTuple critAssociation{
+        "health_rollup", "critical",
+        "/xyz/openbmc_project/inventory/system/chassis"};
+
+    if (std::find(association.begin(), association.end(), critAssociation) ==
+        association.end())
+    {
+        association.push_back(critAssociation);
+        DBusValue setAssociationValue = association;
+
+        auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
+                                           interface::dbusProperty, "Set");
+
+        method.append(interface::association, "Associations",
+                      setAssociationValue);
+        _bus.call(method);
+    }
+}
+
 std::vector<std::string> DataInterface::getSystemNames() const
 {
     DBusSubTree subtree;
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index 7a203ae..89b09ed 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -296,6 +296,14 @@
                                bool functional) const = 0;
 
     /**
+     * @brief Sets the critical association on the D-Bus object.
+     *
+     * @param[in] objectPath - The D-Bus object path
+     */
+    virtual void
+        setCriticalAssociation(const std::string& objectPath) const = 0;
+
+    /**
      * @brief Returns the manufacturing QuiesceOnError property
      *
      * @return bool - Manufacturing QuiesceOnError property
@@ -579,6 +587,13 @@
                        bool functional) const override;
 
     /**
+     * @brief Sets the critical association on the D-Bus object.
+     *
+     * @param[in] objectPath - The D-Bus object path
+     */
+    void setCriticalAssociation(const std::string& objectPath) const override;
+
+    /**
      * @brief Returns the manufacturing QuiesceOnError property
      *
      * @return bool - Manufacturing QuiesceOnError property
diff --git a/extensions/openpower-pels/dbus_types.hpp b/extensions/openpower-pels/dbus_types.hpp
index 9532177..edb5637 100644
--- a/extensions/openpower-pels/dbus_types.hpp
+++ b/extensions/openpower-pels/dbus_types.hpp
@@ -9,8 +9,9 @@
 namespace openpower::pels
 {
 
-using DBusValue = std::variant<std::string, bool, std::vector<uint8_t>,
-                               std::vector<std::string>>;
+using DBusValue = std::variant<
+    std::string, bool, std::vector<uint8_t>, std::vector<std::string>,
+    std::vector<std::tuple<std::string, std::string, std::string>>>;
 using DBusProperty = std::string;
 using DBusInterface = std::string;
 using DBusService = std::string;
diff --git a/extensions/openpower-pels/service_indicators.cpp b/extensions/openpower-pels/service_indicators.cpp
index 1c9eff0..fca9f27 100644
--- a/extensions/openpower-pels/service_indicators.cpp
+++ b/extensions/openpower-pels/service_indicators.cpp
@@ -80,6 +80,7 @@
             if (!paths.empty())
             {
                 setNotFunctional(paths);
+                createCriticalAssociation(paths);
                 sai = false;
             }
         }
@@ -242,4 +243,24 @@
     }
 }
 
+void LightPath::createCriticalAssociation(
+    const std::vector<std::string>& inventoryPaths) const
+{
+    for (const auto& path : inventoryPaths)
+    {
+        try
+        {
+            _dataIface.setCriticalAssociation(path);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::INFO>(
+                fmt::format(
+                    "Could not set critical association on object path {} ({})",
+                    path, 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 85adf2b..1cb61a9 100644
--- a/extensions/openpower-pels/service_indicators.hpp
+++ b/extensions/openpower-pels/service_indicators.hpp
@@ -145,6 +145,15 @@
     void setNotFunctional(const std::vector<std::string>& inventoryPaths) const;
 
     /**
+     * @brief Sets the critical association on the passed in
+     *        inventory paths.
+     *
+     * @param[in] inventoryPaths - The inventory D-Bus paths
+     */
+    void createCriticalAssociation(
+        const std::vector<std::string>& inventoryPaths) const;
+
+    /**
      * @brief Checks if the callout priority is one that the policy
      *        may turn on an LED for.
      *
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
index 1116988..11baa4b 100644
--- a/test/openpower-pels/mocks.hpp
+++ b/test/openpower-pels/mocks.hpp
@@ -45,6 +45,8 @@
                 (const override));
     MOCK_METHOD(std::vector<uint8_t>, getSystemIMKeyword, (), (const override));
     MOCK_METHOD(bool, getQuiesceOnError, (), (const override));
+    MOCK_METHOD(void, setCriticalAssociation, (const std::string&),
+                (const override));
 
     void changeHostState(bool newState)
     {
diff --git a/test/openpower-pels/service_indicators_test.cpp b/test/openpower-pels/service_indicators_test.cpp
index 410ee44..2c53fd9 100644
--- a/test/openpower-pels/service_indicators_test.cpp
+++ b/test/openpower-pels/service_indicators_test.cpp
@@ -274,6 +274,10 @@
                     setFunctional("/system/chassis/processor", false))
             .Times(1);
 
+        EXPECT_CALL(dataIface,
+                    setCriticalAssociation("/system/chassis/processor"))
+            .Times(1);
+
         auto data = pelFactory(1, 'O', 0x20, 0xA400, 500);
         PEL pel{data};
 
@@ -336,4 +340,22 @@
 
         lightPath.activate(pel);
     }
+
+    // Test setCriticalAssociation fail
+    {
+        MockDataInterface dataIface;
+        service_indicators::LightPath lightPath{dataIface};
+
+        EXPECT_CALL(dataIface, getInventoryFromLocCode("U42", 0, true))
+            .WillOnce(Return("/system/chassis/processor"));
+
+        EXPECT_CALL(dataIface,
+                    setCriticalAssociation("/system/chassis/processor"))
+            .WillOnce(Throw(std::runtime_error("Fail")));
+
+        auto data = pelFactory(1, 'O', 0x20, 0xA400, 500);
+        PEL pel{data};
+
+        lightPath.activate(pel);
+    }
 }