Associate BMC health sensors with chassis

This change adds association between the sensors and the bmc, such
that the BMC health sensors are ready to be picked up by bmcweb.

Tested: Added a BMC in phosphor-inventory, made a few temporary
changes to bmcweb so it treats BMCs as a chassis and populates
/redfish/v1/Chassis/bmc/Sensor entries, set up webui-vue, saw
the sensor readings in browser.

Change-Id: I1b5b9e9c97b37ecdef916712cb9f345858eb1ada
Signed-off-by: Sui Chen <suichen6@gmail.com>
diff --git a/healthMonitor.hpp b/healthMonitor.hpp
index 4eedfb3..d96287f 100644
--- a/healthMonitor.hpp
+++ b/healthMonitor.hpp
@@ -1,8 +1,11 @@
 #include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
 #include <sdeventplus/clock.hpp>
 #include <sdeventplus/event.hpp>
 #include <sdeventplus/utility/timer.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
 #include <xyz/openbmc_project/Sensor/Threshold/Critical/server.hpp>
 #include <xyz/openbmc_project/Sensor/Threshold/Warning/server.hpp>
 #include <xyz/openbmc_project/Sensor/Value/server.hpp>
@@ -16,6 +19,8 @@
 namespace health
 {
 
+using namespace phosphor::logging;
+
 using Json = nlohmann::json;
 using ValueIface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
 
@@ -25,9 +30,15 @@
 using WarningInterface =
     sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Warning;
 
+using AssociationDefinitionInterface =
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions;
+
 using healthIfaces =
     sdbusplus::server::object::object<ValueIface, CriticalInterface,
-                                      WarningInterface>;
+                                      WarningInterface,
+                                      AssociationDefinitionInterface>;
+
+using AssociationTuple = std::tuple<std::string, std::string, std::string>;
 
 struct HealthConfig
 {
@@ -59,19 +70,20 @@
      * @param[in] objPath - The Dbus path of health sensor
      */
     HealthSensor(sdbusplus::bus::bus& bus, const char* objPath,
-                 HealthConfig& sensorConfig) :
+                 HealthConfig& sensorConfig,
+                 const std::vector<std::string>& bmcIds) :
         healthIfaces(bus, objPath),
         bus(bus), sensorConfig(sensorConfig),
         timerEvent(sdeventplus::Event::get_default()),
         readTimer(timerEvent, std::bind(&HealthSensor::readHealthSensor, this))
     {
-        initHealthSensor();
+        initHealthSensor(bmcIds);
     }
 
     /** @brief list of sensor data values */
     std::deque<double> valQueue;
-
-    void initHealthSensor();
+    /** @brief Initialize sensor, set default value and association */
+    void initHealthSensor(const std::vector<std::string>& bmcIds);
     /** @brief Set sensor value utilization to health sensor D-bus  */
     void setSensorValueToDbus(const double value);
     /** @brief Set Sensor Threshold to D-bus at beginning */
@@ -88,7 +100,6 @@
     sdeventplus::Event timerEvent;
     /** @brief Sensor Read Timer */
     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> readTimer;
-
     /** @brief Read sensor at regular intrval */
     void readHealthSensor();
 };
@@ -109,15 +120,51 @@
      */
     HealthMon(sdbusplus::bus::bus& bus) : bus(bus)
     {
-        // read json file
+        std::vector<std::string> bmcIds = {};
+
+        // Find all BMCs (DBus objects implementing the
+        // Inventory.Item.Bmc interface that may be created by
+        // configuring the Inventory Manager)
+        sdbusplus::message::message msg = bus.new_method_call(
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths");
+
+        // Search term
+        msg.append("/xyz/openbmc_project/inventory/system");
+
+        // Limit the depth to 2. Example of "depth":
+        // /xyz/openbmc_project/inventory/system/chassis has a depth of 1
+        // since it has 1 '/' after "/xyz/openbmc_project/inventory/system".
+        msg.append(2);
+
+        // Must have the Inventory.Item.Bmc interface
+        msg.append(
+            std::vector<std::string>{"xyz.openbmc_project.Inventory.Item.Bmc"});
+
+        sdbusplus::message::message reply = bus.call(msg, 0);
+        if (reply.get_signature() == std::string("as"))
+        {
+            reply.read(bmcIds);
+            log<level::INFO>("BMC inventory found");
+        }
+        else
+        {
+            log<level::WARNING>(
+                "Did not find BMC inventory, cannot create association");
+        }
+
+        // Read JSON file
         sensorConfigs = getHealthConfig();
-        createHealthSensors();
+
+        // Create health sensors
+        createHealthSensors(bmcIds);
     }
 
-    /** @brief Parsing Health config JSON file  */
+    /** @brief Parse Health config JSON file  */
     Json parseConfigFile(std::string configFile);
 
-    /** @brief reading config for each health sensor component */
+    /** @brief Read config for each health sensor component */
     void getConfigData(Json& data, HealthConfig& cfg);
 
     /** @brief Map of the object HealthSensor */
@@ -125,7 +172,7 @@
         healthSensors;
 
     /** @brief Create sensors for health monitoring */
-    void createHealthSensors();
+    void createHealthSensors(const std::vector<std::string>& bmcIds);
 
   private:
     sdbusplus::bus::bus& bus;