monitor: Track fan health in the System object

To prepare for being able to power off the system based on missing fans
or nonfunctional fan sensors, put a global view of this health for all
fans in the System object.  This requires now keeping track of fan
presence.

This information is stored in a map based on the fan name.  It is done
this way, as opposed to just always calling present/functional APIs on
the Fan objects, so that the code that will be using this information
can be tested in isolation without the System or Fan objects.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ieb1d4003bd13cebc806fd06f0064c63ea8ac6180
diff --git a/monitor/fan.cpp b/monitor/fan.cpp
index 525a22b..6abd2da 100644
--- a/monitor/fan.cpp
+++ b/monitor/fan.cpp
@@ -48,7 +48,12 @@
     _monitorDelay(std::get<monitorStartDelayField>(def)),
     _monitorTimer(event, std::bind(std::mem_fn(&Fan::startMonitor), this)),
 #endif
-    _system(system)
+    _system(system),
+    _presenceMatch(bus,
+                   rules::propertiesChanged(util::INVENTORY_PATH + _name,
+                                            util::INV_ITEM_IFACE),
+                   std::bind(std::mem_fn(&Fan::presenceChanged), this,
+                             std::placeholders::_1))
 {
     // Start from a known state of functional
     updateInventory(true);
@@ -92,8 +97,11 @@
     // If it used the JSON config, then it also will do all the work
     // out of fan-monitor-init, after _monitorDelay.
     _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay));
-
 #endif
+
+    // Get the initial presence state
+    _present = util::SDBusPlus::getProperty<bool>(
+        util::INVENTORY_PATH + _name, util::INV_ITEM_IFACE, "Present");
 }
 
 void Fan::startMonitor()
@@ -229,6 +237,8 @@
 
         updateInventory(false);
     }
+
+    _system.fanStatusChange(*this);
 }
 
 void Fan::updateInventory(bool functional)
@@ -248,6 +258,21 @@
     _functional = functional;
 }
 
+void Fan::presenceChanged(sdbusplus::message::message& msg)
+{
+    std::string interface;
+    std::map<std::string, std::variant<bool>> properties;
+
+    msg.read(interface, properties);
+
+    auto presentProp = properties.find("Present");
+    if (presentProp != properties.end())
+    {
+        _present = std::get<bool>(presentProp->second);
+
+        _system.fanStatusChange(*this);
+    }
+}
 } // namespace monitor
 } // namespace fan
 } // namespace phosphor
diff --git a/monitor/fan.hpp b/monitor/fan.hpp
index 537d8fd..a7db524 100644
--- a/monitor/fan.hpp
+++ b/monitor/fan.hpp
@@ -138,6 +138,26 @@
      */
     uint64_t findTargetSpeed();
 
+    /**
+     * @brief Returns the contained TachSensor objects
+     *
+     * @return std::vector<std::shared_ptr<TachSensor>> - The sensors
+     */
+    const std::vector<std::shared_ptr<TachSensor>>& sensors() const
+    {
+        return _sensors;
+    }
+
+    /**
+     * @brief Returns the presence status of the fan
+     *
+     * @return bool - If the fan is present or not
+     */
+    bool present() const
+    {
+        return _present;
+    }
+
   private:
     /**
      * @brief Returns true if the sensor input is not within
@@ -169,6 +189,13 @@
     void startMonitor();
 
     /**
+     * @brief Called when the fan presence property changes on D-Bus
+     *
+     * @param[in] msg - The message from the propertiesChanged signal
+     */
+    void presenceChanged(sdbusplus::message::message& msg);
+
+    /**
      * @brief the dbus object
      */
     sdbusplus::bus::bus& _bus;
@@ -234,6 +261,18 @@
      * @brief Reference to the System object
      */
     System& _system;
+
+    /**
+     * @brief The match object for propertiesChanged signals
+     *        for the inventory item interface to track the
+     *        Present property.
+     */
+    sdbusplus::bus::match::match _presenceMatch;
+
+    /**
+     * @brief The current presence state
+     */
+    bool _present = false;
 };
 
 } // namespace monitor
diff --git a/monitor/system.cpp b/monitor/system.cpp
index 9b93999..41a5750 100644
--- a/monitor/system.cpp
+++ b/monitor/system.cpp
@@ -67,6 +67,7 @@
         setTrustMgr(trustGrps);
         // Clear/set configured fan definitions
         _fans.clear();
+        _fanHealth.clear();
         setFans(fanDefs);
         log<level::INFO>("Configuration reloaded successfully");
     }
@@ -117,7 +118,26 @@
         }
         _fans.emplace_back(
             std::make_unique<Fan>(_mode, _bus, _event, _trust, fanDef, *this));
+
+        updateFanHealth(*(_fans.back()));
     }
 }
 
+void System::updateFanHealth(const Fan& fan)
+{
+    std::vector<bool> sensorStatus;
+    for (const auto& sensor : fan.sensors())
+    {
+        sensorStatus.push_back(sensor->functional());
+    }
+
+    _fanHealth[fan.getName()] =
+        std::make_tuple(fan.present(), std::move(sensorStatus));
+}
+
+void System::fanStatusChange(const Fan& fan)
+{
+    updateFanHealth(fan);
+}
+
 } // namespace phosphor::fan::monitor
diff --git a/monitor/system.hpp b/monitor/system.hpp
index 9757082..7c424a2 100644
--- a/monitor/system.hpp
+++ b/monitor/system.hpp
@@ -62,6 +62,15 @@
     void sighupHandler(sdeventplus::source::Signal&,
                        const struct signalfd_siginfo*);
 
+    /**
+     * @brief Called from the fan when it changes either
+     *        present or functional status to update the
+     *        fan health map.
+     *
+     * @param[in] fan - The fan that changed
+     */
+    void fanStatusChange(const Fan& fan);
+
   private:
     /* The mode of fan monitor */
     Mode _mode;
@@ -79,6 +88,11 @@
     std::vector<std::unique_ptr<Fan>> _fans;
 
     /**
+     * @brief The latest health of all the fans
+     */
+    FanHealth _fanHealth;
+
+    /**
      * @brief Retrieve the configured trust groups
      *
      * @param[in] jsonObj - JSON object to parse from
@@ -109,6 +123,13 @@
      * @param[in] fanDefs - list of fan definitions to create fans monitored
      */
     void setFans(const std::vector<FanDefinition>& fanDefs);
+
+    /**
+     * @brief Updates the fan health map entry for the fan passed in
+     *
+     * @param[in] fan - The fan to update the health map with
+     */
+    void updateFanHealth(const Fan& fan);
 };
 
 } // namespace phosphor::fan::monitor
diff --git a/monitor/types.hpp b/monitor/types.hpp
index 9f69adf..ff46fcd 100644
--- a/monitor/types.hpp
+++ b/monitor/types.hpp
@@ -114,6 +114,11 @@
     std::tuple<std::string, size_t, size_t, size_t, size_t, size_t,
                std::vector<SensorDefinition>, std::optional<Condition>>;
 
+constexpr auto presentHealthPos = 0;
+constexpr auto sensorFuncHealthPos = 1;
+
+using FanHealthEntry = std::tuple<bool, std::vector<bool>>;
+using FanHealth = std::map<std::string, FanHealthEntry>;
 } // namespace monitor
 } // namespace fan
 } // namespace phosphor
diff --git a/utility.hpp b/utility.hpp
index 4e8c226..271bb9c 100644
--- a/utility.hpp
+++ b/utility.hpp
@@ -31,6 +31,8 @@
     "xyz.openbmc_project.State.Decorator.OperationalStatus";
 constexpr auto FUNCTIONAL_PROPERTY = "Functional";
 
+constexpr auto INV_ITEM_IFACE = "xyz.openbmc_project.Inventory.Item";
+
 class FileDescriptor
 {
   public: