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