Add chassis association to sensors

Add an association to the chassis for each sensor so that the Redfish
code can find them.  The forward association is 'chassis', and the
reverse association is 'all_sensors'.  This matches what other sensor
applications do and is documented in sensors-architecture.md in the docs
repository.

Tested: Checked the associations were present in the mapper and that the
sensors now show up on the sensors page in the web UI.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iefb6d4dc2e5c9399d919e9e1b7c8dd04f20e7a3e
diff --git a/occ_dbus.cpp b/occ_dbus.cpp
index dde2e7d..0b44181 100644
--- a/occ_dbus.cpp
+++ b/occ_dbus.cpp
@@ -2,6 +2,8 @@
 
 #include "utils.hpp"
 
+#include <fmt/core.h>
+
 #include <phosphor-logging/log.hpp>
 
 #include <iostream>
@@ -14,6 +16,11 @@
 {
 
 using namespace phosphor::logging;
+using namespace std::string_literals;
+const auto defaultChassisPath =
+    "/xyz/openbmc_project/inventory/system/chassis"s;
+const auto chassisInterface = "xyz.openbmc_project.Inventory.Item.Chassis"s;
+
 bool OccDBusSensors::setMaxValue(const std::string& path, double value)
 {
     if (path.empty())
@@ -166,6 +173,68 @@
     throw std::invalid_argument("Failed to get OperationalStatus property.");
 }
 
+void OccDBusSensors::setChassisAssociation(const std::string& path)
+{
+    using AssociationsEntry = std::tuple<std::string, std::string, std::string>;
+    using AssociationsProperty = std::vector<AssociationsEntry>;
+    using PropVariant = sdbusplus::xyz::openbmc_project::Association::server::
+        Definitions::PropertiesVariant;
+
+    if (chassisPath.empty())
+    {
+        chassisPath = getChassisPath();
+    }
+
+    AssociationsProperty associations{
+        AssociationsEntry{"chassis", "all_sensors", chassisPath}};
+    PropVariant value{std::move(associations)};
+
+    std::map<std::string, PropVariant> properties;
+    properties.emplace("Associations", std::move(value));
+
+    chassisAssociations.emplace(
+        path, std::make_unique<AssociationIntf>(utils::getBus(), path.c_str(),
+                                                properties));
+}
+
+std::string OccDBusSensors::getChassisPath()
+{
+    try
+    {
+        auto paths = utils::getSubtreePaths(std::vector{chassisInterface});
+
+        // For now, support either 1 chassis, or multiple as long as one
+        // of them has the standard name, which we will use.  If this ever
+        // fails, then someone would have to figure out how to identify the
+        // chassis the OCCs are on.
+        if (paths.size() == 1)
+        {
+            return paths[0];
+        }
+        else if (std::find(paths.begin(), paths.end(), defaultChassisPath) ==
+                 paths.end())
+        {
+            log<level::ERR>(
+                fmt::format(
+                    "Could not find a chassis out of {} chassis objects",
+                    paths.size())
+                    .c_str());
+            // Can't throw an exception here, the sdeventplus timer
+            // just catches it.
+            abort();
+        }
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Error looking up chassis objects: {}", e.what())
+                .c_str());
+        abort();
+    }
+
+    return defaultChassisPath;
+}
+
 } // namespace dbus
 } // namespace occ
 } // namespace open_power