redfish: Add Chassis listing associated drive

If chassis has drives a drive url is added to the chassis, of the form:
redfish/v1/Chassis/<chassis>/Drives

When queried, the drive URL will list all drives associated with the
chassis. This is in accordance with the redfish schema.

Samples for the following URLs are below

wget -qO- http://localhost:80/redfish/v1/Chassis/DC_SCM/Drives
{
  "@odata.id": "/redfish/v1/Chassis/DC_SCM/Drives",
  "@odata.type": "#DriveCollection.DriveCollection",
  "Members": [
    {
      "@odata.id": "/redfish/v1/Chassis/DC_SCM/Drives/mmcblk0"
    }
  ],
  "Members@odata.count": "1",
  "Name": "Drive Collection"
}

Tested:
With the redfish validator: No new errors

Change-Id: Ibdbe7fee5014d6515a77683c8eaca9ca86b6b148
Signed-off-by: John Edward Broadbent <jebr@google.com>
diff --git a/redfish-core/lib/storage.hpp b/redfish-core/lib/storage.hpp
index 998ae60..a276be1 100644
--- a/redfish-core/lib/storage.hpp
+++ b/redfish-core/lib/storage.hpp
@@ -571,4 +571,121 @@
                 "xyz.openbmc_project.Inventory.Item.Drive"});
         });
 }
+
+/**
+ * Chassis drives, this URL will show all the DriveCollection
+ * information
+ */
+void chassisDriveCollectionGet(
+    crow::App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& chassisId)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
+    {
+        return;
+    }
+
+    // mapper call lambda
+    crow::connections::systemBus->async_method_call(
+        [asyncResp,
+         chassisId](const boost::system::error_code ec,
+                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
+        if (ec)
+        {
+            if (ec == boost::system::errc::host_unreachable)
+            {
+                messages::resourceNotFound(asyncResp->res, "Chassis",
+                                           chassisId);
+                return;
+            }
+            messages::internalError(asyncResp->res);
+            return;
+        }
+
+        // Iterate over all retrieved ObjectPaths.
+        for (const std::pair<
+                 std::string,
+                 std::vector<std::pair<std::string, std::vector<std::string>>>>&
+                 object : subtree)
+        {
+            const std::string& path = object.first;
+            const dbus::utility::MapperGetObject& connectionNames =
+                object.second;
+
+            sdbusplus::message::object_path objPath(path);
+            if (objPath.filename() != chassisId)
+            {
+                continue;
+            }
+
+            if (connectionNames.empty())
+            {
+                BMCWEB_LOG_ERROR << "Got 0 Connection names";
+                continue;
+            }
+
+            asyncResp->res.jsonValue["@odata.type"] =
+                "#DriveCollection.DriveCollection";
+            asyncResp->res.jsonValue["@odata.id"] =
+                crow::utility::urlFromPieces("redfish", "v1",
+                                             "Chassis" + chassisId + "Drives");
+            asyncResp->res.jsonValue["Name"] = "Drive Collection";
+
+            // Association lambda
+            sdbusplus::asio::getProperty<std::vector<std::string>>(
+                *crow::connections::systemBus,
+                "xyz.openbmc_project.ObjectMapper", path + "/drive",
+                "xyz.openbmc_project.Association", "endpoints",
+                [asyncResp, chassisId](const boost::system::error_code ec3,
+                                       const std::vector<std::string>& resp) {
+                if (ec3)
+                {
+                    BMCWEB_LOG_ERROR << "Error in chassis Drive association ";
+                }
+                nlohmann::json& members = asyncResp->res.jsonValue["Members"];
+                // important if array is empty
+                members = nlohmann::json::array();
+
+                std::vector<std::string> leafNames;
+                for (const auto& drive : resp)
+                {
+                    sdbusplus::message::object_path path(drive);
+                    leafNames.push_back(path.filename());
+                }
+
+                std::sort(leafNames.begin(), leafNames.end(),
+                          AlphanumLess<std::string>());
+
+                for (const auto& leafName : leafNames)
+                {
+                    nlohmann::json::object_t member;
+                    member["@odata.id"] = crow::utility::urlFromPieces(
+                        "redfish", "v1", "Chassis", chassisId, "Drives",
+                        leafName);
+                    members.push_back(std::move(member));
+                    // navigation links will be registered in next patch set
+                }
+                asyncResp->res.jsonValue["Members@odata.count"] = resp.size();
+                }); // end association lambda
+
+        } // end Iterate over all retrieved ObjectPaths
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/xyz/openbmc_project/inventory", 0,
+        std::array<const char*, 2>{
+            "xyz.openbmc_project.Inventory.Item.Board",
+            "xyz.openbmc_project.Inventory.Item.Chassis"});
+}
+
+inline void requestRoutesChassisDrive(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/")
+        .privileges(redfish::privileges::getDriveCollection)
+        .methods(boost::beast::http::verb::get)(
+            std::bind_front(chassisDriveCollectionGet, std::ref(app)));
+}
+
 } // namespace redfish