Storage: Add Storage Controller

This adds support for Storage Controllers.

Tested: Validator passed

{
    "@odata.context": "/redfish/v1/$metadata#Storage.Storage",
    "@odata.id": "/redfish/v1/Systems/system/Storage/1",
    "@odata.type": "#Storage.v1_7_1.Storage",
    "Drives": [
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_1"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_2"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_3"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_4"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_5"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_6"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_7"
        },
        {
            "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drives/Drive_8"
        }
    ],
    "Drives@odata.count": 8,
    "Id": "1",
    "Name": "Storage Controller",
    "Status": {
        "Health": "OK",
        "HealthRollup": "OK",
        "State": "Enabled"
    },
    "StorageControllers": [
        {
            "@odata.context": "/redfish/v1/$metadata#Storage.StorageController",
            "@odata.id": "/redfish/v1/Systems/system/Storage/1#/StorageControllers/0",
            "@odata.type": "#Storage.v1_7_0.StorageController",
            "Manufacturer": "$BOARD_MANUFACTURER",
            "MemberId": "HSBP_1",
            "Model": "$BOARD_PRODUCT_NAME",
            "Name": "HSBP_1",
            "PartNumber": "$BOARD_PART_NUMBER",
            "SerialNumber": "$BOARD_SERIAL_NUMBER",
            "Status": {
                "Health": "OK",
                "HealthRollup": "OK",
                "State": "Enabled"
            }
        }
    ]
}

Change-Id: I9d956343daa74ddfa912e3cbe0d38b0e42a4859f
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in
index 4e6f6b6..ae2155a 100644
--- a/CMakeLists.txt.in
+++ b/CMakeLists.txt.in
@@ -46,7 +46,7 @@
 
 externalproject_add (
     nlohmann-json GIT_REPOSITORY "https://github.com/nlohmann/json.git" GIT_TAG
-    aafad2be1f3cd259a1e79d2f6fcf267d1ede9ec7 SOURCE_DIR
+    ea60d40f4a60a47d3be9560d8f7bc37c163fe47b SOURCE_DIR
     "${CMAKE_BINARY_DIR}/nlohmann-json-src" BINARY_DIR
     "${CMAKE_BINARY_DIR}/nlohmann-json-build" CONFIGURE_COMMAND "" BUILD_COMMAND
     "" INSTALL_COMMAND mkdir -p "${CMAKE_BINARY_DIR}/prefix/include/nlohmann" &&
diff --git a/redfish-core/lib/storage.hpp b/redfish-core/lib/storage.hpp
index 9c51975..856ef5e 100644
--- a/redfish-core/lib/storage.hpp
+++ b/redfish-core/lib/storage.hpp
@@ -16,6 +16,7 @@
 #pragma once
 
 #include "health.hpp"
+#include "openbmc_dbus_rest.hpp"
 
 #include <node.hpp>
 
@@ -74,19 +75,22 @@
         res.jsonValue["@odata.context"] =
             "/redfish/v1/$metadata#Storage.Storage";
         res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/Storage/1";
-        res.jsonValue["Name"] = "Storage Controller";
+        res.jsonValue["Name"] = "Storage";
         res.jsonValue["Id"] = "1";
         res.jsonValue["Status"]["State"] = "Enabled";
 
         auto asyncResp = std::make_shared<AsyncResp>(res);
+        auto health = std::make_shared<HealthPopulate>(asyncResp);
+        health->populate();
+
         crow::connections::systemBus->async_method_call(
-            [asyncResp](const boost::system::error_code ec,
-                        const std::vector<std::string> &storageList) {
+            [asyncResp, health](const boost::system::error_code ec,
+                                const std::vector<std::string> &storageList) {
                 nlohmann::json &storageArray =
                     asyncResp->res.jsonValue["Drives"];
                 storageArray = nlohmann::json::array();
-                asyncResp->res.jsonValue["Drives@odata.count"] = 0;
-                auto health = std::make_shared<HealthPopulate>(asyncResp);
+                auto &count = asyncResp->res.jsonValue["Drives@odata.count"];
+                count = 0;
 
                 if (ec)
                 {
@@ -95,8 +99,9 @@
                     return;
                 }
 
-                health->inventory = storageList;
-                health->populate();
+                health->inventory.insert(health->inventory.end(),
+                                         storageList.begin(),
+                                         storageList.end());
 
                 for (const std::string &objpath : storageList)
                 {
@@ -114,8 +119,7 @@
                               objpath.substr(lastPos + 1)}});
                 }
 
-                asyncResp->res.jsonValue["Drives@odata.count"] =
-                    storageArray.size();
+                count = storageArray.size();
             },
             "xyz.openbmc_project.ObjectMapper",
             "/xyz/openbmc_project/object_mapper",
@@ -123,6 +127,154 @@
             "/xyz/openbmc_project/inventory", int32_t(0),
             std::array<const char *, 1>{
                 "xyz.openbmc_project.Inventory.Item.Drive"});
+
+        crow::connections::systemBus->async_method_call(
+            [asyncResp,
+             health](const boost::system::error_code ec,
+                     const crow::openbmc_mapper::GetSubTreeType &subtree) {
+                if (ec || !subtree.size())
+                {
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                nlohmann::json &root =
+                    asyncResp->res.jsonValue["StorageControllers"];
+                root = nlohmann::json::array();
+                for (const auto &[path, interfaceDict] : subtree)
+                {
+                    std::size_t lastPos = path.rfind("/");
+                    if (lastPos == std::string::npos ||
+                        (path.size() <= lastPos + 1))
+                    {
+                        BMCWEB_LOG_ERROR << "Failed to find '/' in " << path;
+                        return;
+                    }
+
+                    if (interfaceDict.size() != 1)
+                    {
+                        BMCWEB_LOG_ERROR << "Connection size "
+                                         << interfaceDict.size()
+                                         << ", greater than 1";
+                        messages::internalError(asyncResp->res);
+                        return;
+                    }
+
+                    const std::string &connectionName =
+                        interfaceDict.front().first;
+
+                    size_t index = root.size();
+                    nlohmann::json &storageController =
+                        root.emplace_back(nlohmann::json::object());
+
+                    std::string id = path.substr(lastPos + 1);
+
+                    storageController["@odata.type"] =
+                        "#Storage.v1_7_0.StorageController";
+                    storageController["@odata.context"] =
+                        "/redfish/v1/$metadata#Storage.StorageController";
+                    storageController["@odata.id"] =
+                        "/redfish/v1/Systems/system/Storage/1"
+                        "#/StorageControllers/" +
+                        std::to_string(index);
+                    storageController["Name"] = id;
+                    storageController["MemberId"] = id;
+                    storageController["Status"]["State"] = "Enabled";
+
+                    crow::connections::systemBus->async_method_call(
+                        [asyncResp, index](const boost::system::error_code ec,
+                                           const std::variant<bool> present) {
+                            // this interface isn't necessary, only check it if
+                            // we get a good return
+                            if (ec)
+                            {
+                                return;
+                            }
+                            const bool *enabled = std::get_if<bool>(&present);
+                            if (enabled == nullptr)
+                            {
+                                BMCWEB_LOG_DEBUG << "Illegal property present";
+                                messages::internalError(asyncResp->res);
+                                return;
+                            }
+                            if (!(*enabled))
+                            {
+                                asyncResp->res
+                                    .jsonValue["StorageControllers"][index]
+                                              ["Status"]["State"] = "Disabled";
+                            }
+                        },
+                        connectionName, path, "org.freedesktop.DBus.Properties",
+                        "Get", "xyz.openbmc_project.Inventory.Item", "Present");
+
+                    crow::connections::systemBus->async_method_call(
+                        [asyncResp,
+                         index](const boost::system::error_code ec,
+                                const std::vector<std::pair<
+                                    std::string,
+                                    std::variant<bool, std::string, uint64_t>>>
+                                    &propertiesList) {
+                            if (ec)
+                            {
+                                // this interface isn't necessary
+                                return;
+                            }
+                            for (const std::pair<
+                                     std::string,
+                                     std::variant<bool, std::string, uint64_t>>
+                                     &property : propertiesList)
+                            {
+                                // Store DBus properties that are also
+                                // Redfish properties with same name and a
+                                // string value
+                                const std::string &propertyName =
+                                    property.first;
+                                nlohmann::json &object =
+                                    asyncResp->res
+                                        .jsonValue["StorageControllers"][index];
+                                if ((propertyName == "PartNumber") ||
+                                    (propertyName == "SerialNumber") ||
+                                    (propertyName == "Manufacturer") ||
+                                    (propertyName == "Model"))
+                                {
+                                    const std::string *value =
+                                        std::get_if<std::string>(
+                                            &property.second);
+                                    if (value == nullptr)
+                                    {
+                                        // illegal property
+                                        messages::internalError(asyncResp->res);
+                                        continue;
+                                    }
+                                    object[propertyName] = *value;
+                                }
+                            }
+                        },
+                        connectionName, path, "org.freedesktop.DBus.Properties",
+                        "GetAll",
+                        "xyz.openbmc_project.Inventory.Decorator.Asset");
+                }
+
+                // this is done after we know the json array will no longer be
+                // resized, as json::array uses vector underneath and we need
+                // references to its members that won't change
+                size_t count = 0;
+                for (const auto &[path, interfaceDict] : subtree)
+                {
+                    auto subHealth = std::make_shared<HealthPopulate>(
+                        asyncResp, root[count]["Status"]);
+                    subHealth->inventory.emplace_back(path);
+                    health->inventory.emplace_back(path);
+                    health->children.emplace_back(subHealth);
+                    count++;
+                }
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+            "/xyz/openbmc_project/inventory", int32_t(0),
+            std::array<const char *, 1>{
+                "xyz.openbmc_project.Inventory.Item.StorageController"});
     }
 };
 
@@ -146,17 +298,18 @@
     void doGet(crow::Response &res, const crow::Request &req,
                const std::vector<std::string> &params) override
     {
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+        if (params.size() != 1)
+        {
+            messages::internalError(asyncResp->res);
+            return;
+        }
         const std::string &driveId = params[0];
 
-        auto asyncResp = std::make_shared<AsyncResp>(res);
-
         crow::connections::systemBus->async_method_call(
-            [asyncResp, driveId](
-                const boost::system::error_code ec,
-                const std::vector<std::pair<
-                    std::string, std::vector<std::pair<
-                                     std::string, std::vector<std::string>>>>>
-                    &subtree) {
+            [asyncResp,
+             driveId](const boost::system::error_code ec,
+                      const crow::openbmc_mapper::GetSubTreeType &subtree) {
                 if (ec)
                 {
                     BMCWEB_LOG_ERROR << "Drive mapper call error";
@@ -251,8 +404,7 @@
                 asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
 
                 auto health = std::make_shared<HealthPopulate>(asyncResp);
-                health->inventory = std::vector<std::string>{path};
-
+                health->inventory.emplace_back(path);
                 health->populate();
 
                 crow::connections::systemBus->async_method_call(
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 4299227..0e6cb09 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -1700,10 +1700,11 @@
         };
         auto asyncResp = std::make_shared<AsyncResp>(res);
 
-        constexpr const std::array<const char *, 3> inventoryForSystems = {
+        constexpr const std::array<const char *, 4> inventoryForSystems = {
             "xyz.openbmc_project.Inventory.Item.Dimm",
             "xyz.openbmc_project.Inventory.Item.Cpu",
-            "xyz.openbmc_project.Inventory.Item.Drive"};
+            "xyz.openbmc_project.Inventory.Item.Drive",
+            "xyz.openbmc_project.Inventory.Item.StorageController"};
 
         auto health = std::make_shared<HealthPopulate>(asyncResp);
         crow::connections::systemBus->async_method_call(