Add Fan Redundancy Support

This adds fan redundancy support to passive sensors.
If there are redundancy interfaces on dbus, we'll fail
a sensor if the status is set to failed.

Tested: Set Redundancy to Failed On Dbus, saw all fans
in collection boost. Then restarted swampd, came up
and still boosted. Set redundancy OK, and they all slowed
down.

Change-Id: I8879bef1471bbc168435d6b22dd20006b9dca133
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 241878c..3283236 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -79,6 +79,7 @@
 	notimpl/readonly.cpp \
 	notimpl/writeonly.cpp \
 	dbus/util.cpp \
+	dbus/dbuspassiveredundancy.cpp \
 	dbus/dbuspassive.cpp \
 	dbus/dbusactiveread.cpp \
 	dbus/dbuswrite.cpp \
diff --git a/dbus/dbuspassive.cpp b/dbus/dbuspassive.cpp
index a154b02..a0dbc57 100644
--- a/dbus/dbuspassive.cpp
+++ b/dbus/dbuspassive.cpp
@@ -15,6 +15,7 @@
  */
 #include "dbuspassive.hpp"
 
+#include "dbuspassiveredundancy.hpp"
 #include "util.hpp"
 
 #include <chrono>
@@ -27,7 +28,8 @@
 
 std::unique_ptr<ReadInterface> DbusPassive::createDbusPassive(
     sdbusplus::bus::bus& bus, const std::string& type, const std::string& id,
-    DbusHelperInterface* helper, const conf::SensorConfig* info)
+    DbusHelperInterface* helper, const conf::SensorConfig* info,
+    const std::shared_ptr<DbusPassiveRedundancy>& redundancy)
 {
     if (helper == nullptr)
     {
@@ -69,15 +71,19 @@
     }
 
     return std::make_unique<DbusPassive>(bus, type, id, helper, settings,
-                                         failed);
+                                         failed, path, redundancy);
 }
 
-DbusPassive::DbusPassive(sdbusplus::bus::bus& bus, const std::string& type,
-                         const std::string& id, DbusHelperInterface* helper,
-                         const struct SensorProperties& settings, bool failed) :
+DbusPassive::DbusPassive(
+    sdbusplus::bus::bus& bus, const std::string& type, const std::string& id,
+    DbusHelperInterface* helper, const struct SensorProperties& settings,
+    bool failed, const std::string& path,
+    const std::shared_ptr<DbusPassiveRedundancy>& redundancy) :
     ReadInterface(),
     _bus(bus), _signal(bus, getMatch(type, id).c_str(), dbusHandleSignal, this),
-    _id(id), _helper(helper), _failed(failed)
+    _id(id), _helper(helper), _failed(failed), path(path),
+    redundancy(redundancy)
+
 {
     _scale = settings.scale;
     _value = settings.value * pow(10, _scale);
@@ -105,6 +111,14 @@
 
 bool DbusPassive::getFailed(void) const
 {
+    if (redundancy)
+    {
+        const std::set<std::string>& failures = redundancy->getFailed();
+        if (failures.find(path) != failures.end())
+        {
+            return true;
+        }
+    }
     return _failed;
 }
 
diff --git a/dbus/dbuspassive.hpp b/dbus/dbuspassive.hpp
index ebc831d..f00a2ea 100644
--- a/dbus/dbuspassive.hpp
+++ b/dbus/dbuspassive.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "conf.hpp"
+#include "dbuspassiveredundancy.hpp"
 #include "interfaces.hpp"
 #include "util.hpp"
 
@@ -34,14 +35,17 @@
 class DbusPassive : public ReadInterface
 {
   public:
-    static std::unique_ptr<ReadInterface>
-        createDbusPassive(sdbusplus::bus::bus& bus, const std::string& type,
-                          const std::string& id, DbusHelperInterface* helper,
-                          const conf::SensorConfig* info);
+    static std::unique_ptr<ReadInterface> createDbusPassive(
+        sdbusplus::bus::bus& bus, const std::string& type,
+        const std::string& id, DbusHelperInterface* helper,
+        const conf::SensorConfig* info,
+        const std::shared_ptr<DbusPassiveRedundancy>& redundancy);
 
     DbusPassive(sdbusplus::bus::bus& bus, const std::string& type,
                 const std::string& id, DbusHelperInterface* helper,
-                const struct SensorProperties& settings, bool failed);
+                const struct SensorProperties& settings, bool failed,
+                const std::string& path,
+                const std::shared_ptr<DbusPassiveRedundancy>& redundancy);
 
     ReadReturn read(void) override;
     bool getFailed(void) const override;
@@ -65,6 +69,9 @@
     double _max = 0;
     double _min = 0;
     bool _failed = false;
+
+    std::string path;
+    std::shared_ptr<DbusPassiveRedundancy> redundancy;
     /* The last time the value was refreshed, not necessarily changed. */
     std::chrono::high_resolution_clock::time_point _updated;
 };
diff --git a/dbus/dbuspassiveredundancy.cpp b/dbus/dbuspassiveredundancy.cpp
new file mode 100644
index 0000000..c7a1d0f
--- /dev/null
+++ b/dbus/dbuspassiveredundancy.cpp
@@ -0,0 +1,177 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "dbuspassiveredundancy.hpp"
+
+#include <iostream>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <set>
+#include <unordered_map>
+#include <variant>
+
+namespace properties
+{
+
+constexpr const char* interface = "org.freedesktop.DBus.Properties";
+constexpr const char* get = "Get";
+constexpr const char* getAll = "GetAll";
+
+} // namespace properties
+
+namespace redundancy
+{
+
+constexpr const char* collection = "Collection";
+constexpr const char* status = "Status";
+constexpr const char* interface = "xyz.openbmc_project.Control.FanRedundancy";
+
+} // namespace redundancy
+
+DbusPassiveRedundancy::DbusPassiveRedundancy(sdbusplus::bus::bus& bus) :
+    match(bus,
+          "type='signal',member='PropertiesChanged',arg0namespace='" +
+              std::string(redundancy::interface) + "'",
+          std::move([this](sdbusplus::message::message& message) {
+              std::string objectName;
+              std::unordered_map<
+                  std::string,
+                  std::variant<std::string, std::vector<std::string>>>
+                  result;
+              try
+              {
+                  message.read(objectName, result);
+              }
+              catch (sdbusplus::exception_t&)
+              {
+                  std::cerr << "Error reading match data";
+                  return;
+              }
+              auto findStatus = result.find("Status");
+              if (findStatus == result.end())
+              {
+                  return;
+              }
+              std::string status = std::get<std::string>(findStatus->second);
+
+              auto methodCall = passiveBus.new_method_call(
+                  message.get_sender(), message.get_path(),
+                  properties::interface, properties::get);
+              methodCall.append(redundancy::interface, redundancy::collection);
+              std::variant<std::vector<std::string>> collection;
+
+              try
+              {
+                  auto reply = passiveBus.call(methodCall);
+                  reply.read(collection);
+              }
+              catch (sdbusplus::exception_t&)
+              {
+                  std::cerr << "Error reading match data";
+                  return;
+              }
+
+              auto data = std::get<std::vector<std::string>>(collection);
+              if (status.rfind("Failed") != std::string::npos)
+              {
+                  failed.insert(data.begin(), data.end());
+              }
+              else
+              {
+                  for (const auto& d : data)
+                  {
+                      failed.erase(d);
+                  }
+              }
+          })),
+    passiveBus(bus)
+{
+    populateFailures();
+}
+
+void DbusPassiveRedundancy::populateFailures(void)
+{
+    auto mapper = passiveBus.new_method_call(
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+    mapper.append("/", 0, std::array<const char*, 1>{redundancy::interface});
+    std::unordered_map<
+        std::string, std::unordered_map<std::string, std::vector<std::string>>>
+        respData;
+    try
+    {
+        auto resp = passiveBus.call(mapper);
+        resp.read(respData);
+    }
+    catch (sdbusplus::exception_t&)
+    {
+        std::cerr << "Populate Failures Mapper Error\n";
+        return;
+    }
+
+    /*
+     * The subtree response looks like:
+     * {path :
+     *     {busname:
+     *        {interface, interface, interface, ...}
+     *     }
+     * }
+     *
+     * This loops through this structure to pre-poulate the already failed items
+     */
+
+    for (const auto& [path, interfaceDict] : respData)
+    {
+        for (const auto& [owner, _] : interfaceDict)
+        {
+            auto call = passiveBus.new_method_call(owner.c_str(), path.c_str(),
+                                                   properties::interface,
+                                                   properties::getAll);
+            call.append(redundancy::interface);
+
+            std::unordered_map<
+                std::string,
+                std::variant<std::string, std::vector<std::string>>>
+                getAll;
+            try
+            {
+                auto data = passiveBus.call(call);
+                data.read(getAll);
+            }
+            catch (sdbusplus::exception_t&)
+            {
+                std::cerr << "Populate Failures Mapper Error\n";
+                return;
+            }
+            std::string status =
+                std::get<std::string>(getAll[redundancy::status]);
+            if (status.rfind("Failed") == std::string::npos)
+            {
+                continue;
+            }
+            std::vector<std::string> collection =
+                std::get<std::vector<std::string>>(
+                    getAll[redundancy::collection]);
+            failed.insert(collection.begin(), collection.end());
+        }
+    }
+}
+
+const std::set<std::string>& DbusPassiveRedundancy::getFailed()
+{
+    return failed;
+}
\ No newline at end of file
diff --git a/dbus/dbuspassiveredundancy.hpp b/dbus/dbuspassiveredundancy.hpp
new file mode 100644
index 0000000..3d7e2a6
--- /dev/null
+++ b/dbus/dbuspassiveredundancy.hpp
@@ -0,0 +1,43 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <set>
+
+/*
+ * DbusPassiveRedundancy monitors the fan redundancy interface via dbus match
+ * for changes. When the "Status" property changes to  Failed, all sensors in
+ * the interface's "Collection" property are added to the failed member. This
+ * set can then be queried by a sensor to see if they are part of a failed
+ * redundancy collection. When this happens, they get marked as failed.
+ */
+
+class DbusPassiveRedundancy
+{
+  public:
+    DbusPassiveRedundancy(sdbusplus::bus::bus& bus);
+    const std::set<std::string>& getFailed(void);
+
+  private:
+    void populateFailures(void);
+
+    sdbusplus::bus::match::match match;
+    std::set<std::string> failed;
+    sdbusplus::bus::bus& passiveBus;
+};
diff --git a/sensors/builder.cpp b/sensors/builder.cpp
index f5c01b3..4da1cf2 100644
--- a/sensors/builder.cpp
+++ b/sensors/builder.cpp
@@ -68,8 +68,23 @@
         switch (rtype)
         {
             case IOInterfaceType::DBUSPASSIVE:
-                ri = DbusPassive::createDbusPassive(
-                    passiveListeningBus, info->type, name, &helper, info);
+                // we only need to make one match based on the dbus object
+                static std::shared_ptr<DbusPassiveRedundancy> redundancy =
+                    std::make_shared<DbusPassiveRedundancy>(
+                        passiveListeningBus);
+
+                if (info->type == "fan")
+                {
+                    ri = DbusPassive::createDbusPassive(
+                        passiveListeningBus, info->type, name, &helper, info,
+                        redundancy);
+                }
+                else
+                {
+                    ri = DbusPassive::createDbusPassive(passiveListeningBus,
+                                                        info->type, name,
+                                                        &helper, info, nullptr);
+                }
                 if (ri == nullptr)
                 {
                     throw SensorBuildException(
diff --git a/test/Makefile.am b/test/Makefile.am
index fbc78bf..3d74c88 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -63,7 +63,7 @@
 
 dbus_passive_unittest_SOURCES = dbus_passive_unittest.cpp
 dbus_passive_unittest_LDADD = $(top_builddir)/dbus/util.o \
- $(top_builddir)/dbus/dbuspassive.o
+ $(top_builddir)/dbus/dbuspassive.o $(top_builddir)/dbus/dbuspassiveredundancy.o
 
 dbus_active_unittest_SOURCES = dbus_active_unittest.cpp
 dbus_active_unittest_LDADD = $(top_builddir)/dbus/dbusactiveread.o
diff --git a/test/dbus_passive_unittest.cpp b/test/dbus_passive_unittest.cpp
index 2fc3471..84557ee 100644
--- a/test/dbus_passive_unittest.cpp
+++ b/test/dbus_passive_unittest.cpp
@@ -31,8 +31,8 @@
     DbusHelperMock helper;
     auto info = conf::SensorConfig();
 
-    std::unique_ptr<ReadInterface> ri =
-        DbusPassive::createDbusPassive(bus_mock, type, id, &helper, &info);
+    std::unique_ptr<ReadInterface> ri = DbusPassive::createDbusPassive(
+        bus_mock, type, id, &helper, &info, nullptr);
 
     EXPECT_EQ(ri, nullptr);
 }
@@ -50,7 +50,7 @@
     DbusHelperMock helper;
     struct SensorProperties properties;
 
-    DbusPassive(bus_mock, type, id, &helper, properties, false);
+    DbusPassive(bus_mock, type, id, &helper, properties, false, path, nullptr);
     // Success
 }
 
@@ -77,7 +77,8 @@
             .WillOnce(Return(false));
 
         auto info = conf::SensorConfig();
-        ri = DbusPassive::createDbusPassive(bus_mock, type, id, &helper, &info);
+        ri = DbusPassive::createDbusPassive(bus_mock, type, id, &helper, &info,
+                                            nullptr);
         passive = reinterpret_cast<DbusPassive*>(ri.get());
         EXPECT_FALSE(passive == nullptr);
     }