callback-manager: Add association manager

This adds an association manager based on Redfish health
whitepaper to do a rollup of subcomponent health.

https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/21380

Tested:

Chassis health rollup works with upstream patch 21798

{
    "@odata.context": "/redfish/v1/$metadata#Chassis.Chassis",
    "@odata.id": "/redfish/v1/Chassis/WFP_Baseboard",
    "@odata.type": "#Chassis.v1_4_0.Chassis",
    "ChassisType": "RackMount",
    "Id": "WFP_Baseboard",
    "Links": {
        "ComputerSystems": [
            {
                "@odata.id": "/redfish/v1/Systems/system"
            }
        ],
        "ManagedBy": [
            {
                "@odata.id": "/redfish/v1/Managers/bmc"
            }
        ]
    },
    "Manufacturer": "Intel Corporation",
    "Model": "S2600WFT",
    "Name": "WFP_Baseboard",
    "PartNumber": "123456789",
    "Power": {
        "@odata.id": "/redfish/v1/Chassis/WFP_Baseboard/Power"
    },
    "PowerState": "Off",
    "SerialNumber": "123454321",
    "Status": {
        "Health": "Warning",
        "HealthRollup": "Critical",
        "State": "StandbyOffline"
    },
    "Thermal": {
        "@odata.id": "/redfish/v1/Chassis/WFP_Baseboard/Thermal"
    }
}

Change-Id: I1d944f31749f6eae952573078ad410547ae9faea
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/callback-manager/include/callback_manager.hpp b/callback-manager/include/callback_manager.hpp
new file mode 100644
index 0000000..f66cb4d
--- /dev/null
+++ b/callback-manager/include/callback_manager.hpp
@@ -0,0 +1,94 @@
+#pragma once
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <iostream>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+using Association = std::tuple<std::string, std::string, std::string>;
+
+constexpr const char* rootPath = "/xyz/openbmc_project/CallbackManager";
+constexpr const char* sensorPath = "/xyz/openbmc_project/sensors";
+
+constexpr const char* globalInventoryIface =
+    "xyz.openbmc_project.Inventory.Item.Global";
+constexpr const char* associationIface = "org.openbmc.Associations";
+
+namespace threshold
+{
+constexpr const char* critical = "critical";
+constexpr const char* warning = "warning";
+} // namespace threshold
+
+struct AssociationManager
+{
+    AssociationManager(sdbusplus::asio::object_server& objectServer,
+                       std::shared_ptr<sdbusplus::asio::connection>& conn) :
+        objectServer(objectServer),
+        association(objectServer.add_interface(rootPath, associationIface)),
+        sensorAssociation(
+            objectServer.add_interface(sensorPath, associationIface))
+    {
+        association->register_property("associations", std::set<Association>());
+        sensorAssociation->register_property("associations",
+                                             std::set<Association>());
+        association->initialize();
+        sensorAssociation->initialize();
+    }
+    ~AssociationManager()
+    {
+        objectServer.remove_interface(association);
+        objectServer.remove_interface(sensorAssociation);
+    }
+
+    void setLocalAssociations(const std::vector<std::string>& fatal,
+                              const std::vector<std::string>& critical,
+                              const std::vector<std::string>& warning)
+    {
+        std::set<Association> result;
+
+        // fatal maps to redfish critical as refish only has 3 states and LED
+        // has 4
+        for (const std::string& path : fatal)
+        {
+            result.emplace(threshold::critical, "", path);
+        }
+        for (const std::string& path : critical)
+        {
+            result.emplace(threshold::warning, "", path);
+        }
+        for (const std::string& path : warning)
+        {
+            result.emplace(threshold::warning, "", path);
+        }
+        setSensorAssociations(critical, warning);
+        association->set_property("associations", result);
+    }
+
+    void setSensorAssociations(const std::vector<std::string>& critical,
+                               const std::vector<std::string>& warning)
+    {
+        std::set<Association> result;
+        for (const std::string& path : critical)
+        {
+            if (!boost::starts_with(path, sensorPath))
+            {
+                continue;
+            }
+            result.emplace(threshold::critical, "", path);
+        }
+        for (const std::string& path : warning)
+        {
+            if (!boost::starts_with(path, sensorPath))
+            {
+                continue;
+            }
+            result.emplace(threshold::warning, "", path);
+        }
+        sensorAssociation->set_property("associations", result);
+    }
+
+    sdbusplus::asio::object_server& objectServer;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> association;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> sensorAssociation;
+};
\ No newline at end of file
diff --git a/callback-manager/src/callback_manager.cpp b/callback-manager/src/callback_manager.cpp
index 34506d9..ba59306 100644
--- a/callback-manager/src/callback_manager.cpp
+++ b/callback-manager/src/callback_manager.cpp
@@ -14,8 +14,9 @@
 // limitations under the License.
 */
 
+#include "callback_manager.hpp"
+
 #include <boost/container/flat_map.hpp>
-#include <iostream>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
 #include <variant>
@@ -33,6 +34,8 @@
 constexpr const char* ledManagerBusname =
     "xyz.openbmc_project.LED.GroupManager";
 
+std::unique_ptr<AssociationManager> associationManager;
+
 enum class StatusSetting
 {
     none,
@@ -42,8 +45,6 @@
     fatal
 };
 
-std::shared_ptr<sdbusplus::asio::dbus_interface> assertedIface = nullptr;
-
 constexpr const bool debug = false;
 
 // final led state tracking
@@ -82,18 +83,17 @@
 void updateLedStatus(std::shared_ptr<sdbusplus::asio::connection>& conn,
                      bool forceRefresh = false)
 {
-    std::vector<std::string> assertedVector = assertedInMap(fatalAssertMap);
-    assertedIface->set_property("Fatal", assertedVector);
-    bool fatal = assertedVector.size();
+    std::vector<std::string> fatalVector = assertedInMap(fatalAssertMap);
+    bool fatal = fatalVector.size();
 
-    assertedVector = assertedInMap(criticalAssertMap);
-    assertedIface->set_property("Critical", assertedVector);
-    bool critical = assertedVector.size();
+    std::vector<std::string> criticalVector = assertedInMap(criticalAssertMap);
+    bool critical = criticalVector.size();
 
-    assertedVector = assertedInMap(warningAssertMap);
-    assertedIface->set_property("Warning", assertedVector);
+    std::vector<std::string> warningVector = assertedInMap(warningAssertMap);
+    bool warn = warningVector.size();
 
-    bool warn = assertedVector.size();
+    associationManager->setLocalAssociations(fatalVector, criticalVector,
+                                             warningVector);
 
     StatusSetting last = currentPriority;
 
@@ -237,15 +237,18 @@
     auto conn = std::make_shared<sdbusplus::asio::connection>(io);
     conn->request_name("xyz.openbmc_project.CallbackManager");
     sdbusplus::asio::object_server objServer(conn);
-    assertedIface =
-        objServer.add_interface("/xyz/openbmc_project/CallbackManager",
+    std::shared_ptr<sdbusplus::asio::dbus_interface> rootIface =
+        objServer.add_interface(rootPath,
                                 "xyz.openbmc_project.CallbackManager");
-    assertedIface->register_property("Warning", std::vector<std::string>());
-    assertedIface->register_property("Critical", std::vector<std::string>());
-    assertedIface->register_property("Fatal", std::vector<std::string>());
-    assertedIface->register_method("RetriggerLEDUpdate",
-                                   [&conn]() { updateLedStatus(conn, true); });
-    assertedIface->initialize();
+    rootIface->register_method("RetriggerLEDUpdate",
+                               [&conn]() { updateLedStatus(conn, true); });
+    rootIface->initialize();
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
+        objServer.add_interface(rootPath, globalInventoryIface);
+    inventoryIface->initialize();
+
+    associationManager = std::make_unique<AssociationManager>(objServer, conn);
 
     createThresholdMatch(conn);
     updateLedStatus(conn);