Health / Rollup Support

Look for associations for inventory and compare the
inventory warning / critical and global warning / critical
to get HealthRollup and Health respectively.

Tested:

Used sensor override to set BMC temp to Upper critical
and saw:

{
    "@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: Idd9e832db18bb4769f1452fe243d68339a6f844d
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp
index ae3201a..4426402 100644
--- a/redfish-core/lib/chassis.hpp
+++ b/redfish-core/lib/chassis.hpp
@@ -15,6 +15,7 @@
 */
 #pragma once
 
+#include "health.hpp"
 #include "node.hpp"
 
 #include <boost/container/flat_map.hpp>
@@ -291,6 +292,30 @@
                         continue;
                     }
 
+                    auto health = std::make_shared<HealthPopulate>(asyncResp);
+
+                    crow::connections::systemBus->async_method_call(
+                        [health](const boost::system::error_code ec,
+                                 std::variant<std::vector<std::string>> &resp) {
+                            if (ec)
+                            {
+                                return; // no sensors = no failures
+                            }
+                            std::vector<std::string> *data =
+                                std::get_if<std::vector<std::string>>(&resp);
+                            if (data == nullptr)
+                            {
+                                return;
+                            }
+                            health->inventory = std::move(*data);
+                        },
+                        "xyz.openbmc_project.ObjectMapper",
+                        path + "/all_sensors",
+                        "org.freedesktop.DBus.Properties", "Get",
+                        "xyz.openbmc_project.Association", "endpoints");
+
+                    health->populate();
+
                     if (connectionNames.size() < 1)
                     {
                         BMCWEB_LOG_ERROR << "Only got "
@@ -347,7 +372,6 @@
                                 {"@odata.id", "/redfish/v1/Chassis/" +
                                                   chassisId + "/Power"}};
                             asyncResp->res.jsonValue["Status"] = {
-                                {"Health", "OK"},
                                 {"State", "Enabled"},
                             };
 
diff --git a/redfish-core/lib/health.hpp b/redfish-core/lib/health.hpp
new file mode 100644
index 0000000..da4f2d9
--- /dev/null
+++ b/redfish-core/lib/health.hpp
@@ -0,0 +1,194 @@
+/*
+// 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 "async_resp.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/container/flat_set.hpp>
+#include <dbus_singleton.hpp>
+#include <variant>
+
+namespace redfish
+{
+
+struct HealthPopulate : std::enable_shared_from_this<HealthPopulate>
+{
+    HealthPopulate(const std::shared_ptr<AsyncResp> &asyncResp) :
+        asyncResp(asyncResp)
+    {
+    }
+
+    ~HealthPopulate()
+    {
+        nlohmann::json &health = asyncResp->res.jsonValue["Status"]["Health"];
+        nlohmann::json &rollup =
+            asyncResp->res.jsonValue["Status"]["HealthRollup"];
+
+        health = "OK";
+        rollup = "OK";
+
+        for (const auto &[path, interfaces] : statuses)
+        {
+            bool isChild = false;
+
+            // managers inventory is all the inventory, don't skip any
+            if (!isManagersHealth)
+            {
+
+                // We only want to look at this association if either the path
+                // of this association is an inventory item, or one of the
+                // endpoints in this association is a child
+
+                for (const std::string &child : inventory)
+                {
+                    if (boost::starts_with(path.str, child))
+                    {
+                        isChild = true;
+                        break;
+                    }
+                }
+                if (!isChild)
+                {
+                    auto assocIt =
+                        interfaces.find("xyz.openbmc_project.Association");
+                    if (assocIt == interfaces.end())
+                    {
+                        continue;
+                    }
+                    auto endpointsIt = assocIt->second.find("endpoints");
+                    if (endpointsIt == assocIt->second.end())
+                    {
+                        BMCWEB_LOG_ERROR << "Illegal association at "
+                                         << path.str;
+                        continue;
+                    }
+                    const std::vector<std::string> *endpoints =
+                        std::get_if<std::vector<std::string>>(
+                            &endpointsIt->second);
+                    if (endpoints == nullptr)
+                    {
+                        BMCWEB_LOG_ERROR << "Illegal association at "
+                                         << path.str;
+                        continue;
+                    }
+                    bool containsChild = false;
+                    for (const std::string &endpoint : *endpoints)
+                    {
+                        if (std::find(inventory.begin(), inventory.end(),
+                                      endpoint) != inventory.end())
+                        {
+                            containsChild = true;
+                            break;
+                        }
+                    }
+                    if (!containsChild)
+                    {
+                        continue;
+                    }
+                }
+            }
+
+            if (boost::starts_with(path.str, globalInventoryPath) &&
+                boost::ends_with(path.str, "critical"))
+            {
+                health = "Critical";
+                rollup = "Critical";
+                return;
+            }
+            else if (boost::starts_with(path.str, globalInventoryPath) &&
+                     boost::ends_with(path.str, "warning"))
+            {
+                health = "Warning";
+                if (rollup != "Critical")
+                {
+                    rollup = "Warning";
+                }
+            }
+            else if (boost::ends_with(path.str, "critical"))
+            {
+                rollup = "Critical";
+            }
+            else if (boost::ends_with(path.str, "warning"))
+            {
+                if (rollup != "Critical")
+                {
+                    rollup = "Warning";
+                }
+            }
+        }
+    }
+
+    void populate()
+    {
+        getAllStatusAssociations();
+        getGlobalPath();
+    }
+
+    void getGlobalPath()
+    {
+        std::shared_ptr<HealthPopulate> self = shared_from_this();
+        crow::connections::systemBus->async_method_call(
+            [self](const boost::system::error_code ec,
+                   std::vector<std::string> &resp) {
+                if (ec || resp.size() != 1)
+                {
+                    // no global item, or too many
+                    return;
+                }
+                self->globalInventoryPath = std::move(resp[0]);
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
+            int32_t(0),
+            std::array<const char *, 1>{
+                "xyz.openbmc_project.Inventory.Item.Global"});
+    }
+
+    void getAllStatusAssociations()
+    {
+        std::shared_ptr<HealthPopulate> self = shared_from_this();
+        crow::connections::systemBus->async_method_call(
+            [self](const boost::system::error_code ec,
+                   dbus::utility::ManagedObjectType &resp) {
+                if (ec)
+                {
+                    return;
+                }
+                for (auto it = resp.begin(); it != resp.end();)
+                {
+                    if (boost::ends_with(it->first.str, "critical") ||
+                        boost::ends_with(it->first.str, "warning"))
+                    {
+                        it++;
+                        continue;
+                    }
+                    it = resp.erase(it);
+                }
+                self->statuses = std::move(resp);
+            },
+            "xyz.openbmc_project.ObjectMapper", "/",
+            "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+    }
+
+    std::shared_ptr<AsyncResp> asyncResp;
+    std::vector<std::string> inventory;
+    bool isManagersHealth = false;
+    dbus::utility::ManagedObjectType statuses;
+    std::string globalInventoryPath = "-"; // default to illegal dbus path
+};
+} // namespace redfish
\ No newline at end of file
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index bb9a6ac..60f8856 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -15,6 +15,7 @@
 */
 #pragma once
 
+#include "health.hpp"
 #include "node.hpp"
 #include "redfish_util.hpp"
 
@@ -1522,6 +1523,10 @@
 
         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
 
+        auto health = std::make_shared<HealthPopulate>(asyncResp);
+        health->isManagersHealth = true;
+        health->populate();
+
         crow::connections::systemBus->async_method_call(
             [asyncResp](const boost::system::error_code ec,
                         const dbus::utility::ManagedObjectType& resp) {
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 6ea5f2e..f28ab92 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -15,6 +15,7 @@
 */
 #pragma once
 
+#include "health.hpp"
 #include "redfish_util.hpp"
 
 #include <boost/container/flat_map.hpp>
@@ -1215,6 +1216,29 @@
         };
         auto asyncResp = std::make_shared<AsyncResp>(res);
 
+        constexpr const std::array<const char *, 2> inventoryForSystems = {
+            "xyz.openbmc_project.Inventory.Item.Dimm",
+            "xyz.openbmc_project.Inventory.Item.Cpu"};
+
+        auto health = std::make_shared<HealthPopulate>(asyncResp);
+        crow::connections::systemBus->async_method_call(
+            [health](const boost::system::error_code ec,
+                     std::vector<std::string> &resp) {
+                if (ec)
+                {
+                    // no inventory
+                    return;
+                }
+
+                health->inventory = std::move(resp);
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
+            int32_t(0), inventoryForSystems);
+
+        health->populate();
+
         getMainChassisId(asyncResp, [](const std::string &chassisId,
                                        std::shared_ptr<AsyncResp> aRsp) {
             aRsp->res.jsonValue["Links"]["Chassis"] = {