Add Storage Schema

This takes the original commit below and updates it so that it
passes the validatior, and provides the Status attribute in
redfish when appropriate.

Tested: Passed the validator

{
    "@odata.context": "/redfish/v1/$metadata#Drive.Drive",
    "@odata.id": "/redfish/v1/Systems/system/Storage/1/Drive/Drive_2",
    "@odata.type": "#Drive.v1_2_0.Drive",
    "Id": "Drive_2",
    "Manufacturer": "INTEL",
    "Model": "P4800X",
    "Name": "Drive_2",
    "PartNumber": "INTEL SSDPE21K375GA",
    "SerialNumber": "PHKE722600NL375AGN",
    "Status": {
        "Health": "OK",
        "HealthRollup": "OK",
        "State": "Enabled"
    }
}

Original Commit Message:
-------------------------------------------------------------------

Add Storage Schema for NVMe on Redfish

This provides an implementation for the Get methods for the Storage
schemas using following classes :
- StorageCollection
- Storage

Tested:
- Ran Redfish Service Validator to verify no issues are reported.
- Tested that the NVMe drives in the system show up and proper fields
are populated with appropriate data.
- Tested with no drives present. Made sure the Storage interface shows
no drives and Drive interface returns error message.

Change-Id: Id0306ea413ac16a993110bb1a36cd95d939cff71
Signed-off-by: Nikhil Potade <nikhil.potade@linux.intel.com>
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index bf0f51f..b39ea0b 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -30,6 +30,7 @@
 #include "../lib/roles.hpp"
 #include "../lib/sensors.hpp"
 #include "../lib/service_root.hpp"
+#include "../lib/storage.hpp"
 #include "../lib/systems.hpp"
 #include "../lib/thermal.hpp"
 #include "../lib/update_service.hpp"
@@ -71,6 +72,9 @@
         nodes.emplace_back(std::make_unique<ChassisCollection>(app));
         nodes.emplace_back(std::make_unique<Chassis>(app));
         nodes.emplace_back(std::make_unique<UpdateService>(app));
+        nodes.emplace_back(std::make_unique<StorageCollection>(app));
+        nodes.emplace_back(std::make_unique<Storage>(app));
+        nodes.emplace_back(std::make_unique<Drive>(app));
 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
         nodes.emplace_back(
             std::make_unique<UpdateServiceActionsSimpleUpdate>(app));
diff --git a/redfish-core/lib/storage.hpp b/redfish-core/lib/storage.hpp
new file mode 100644
index 0000000..3c4fe4c
--- /dev/null
+++ b/redfish-core/lib/storage.hpp
@@ -0,0 +1,286 @@
+/*
+// 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 <node.hpp>
+
+namespace redfish
+{
+class StorageCollection : public Node
+{
+  public:
+    StorageCollection(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/system/Storage/")
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        res.jsonValue["@odata.type"] = "#StorageCollection.StorageCollection";
+        res.jsonValue["@odata.context"] =
+            "/redfish/v1/$metadata#StorageCollection.StorageCollection";
+        res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/Storage";
+        res.jsonValue["Name"] = "Storage Collection";
+        res.jsonValue["Members"] = {
+            {{"@odata.id", "/redfish/v1/Systems/system/Storage/1"}}};
+        res.jsonValue["Members@odata.count"] = 1;
+        res.end();
+    }
+};
+
+class Storage : public Node
+{
+  public:
+    Storage(CrowApp &app) : Node(app, "/redfish/v1/Systems/system/Storage/1")
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        res.jsonValue["@odata.type"] = "#Storage.v1_7_1.Storage";
+        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["Id"] = "1";
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+        crow::connections::systemBus->async_method_call(
+            [asyncResp](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;
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "Drive mapper call error";
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                for (const std::string &objpath : storageList)
+                {
+                    std::size_t lastPos = objpath.rfind("/");
+                    if (lastPos == std::string::npos ||
+                        (objpath.size() <= lastPos + 1))
+                    {
+                        BMCWEB_LOG_ERROR << "Failed to find '/' in " << objpath;
+                        continue;
+                    }
+
+                    storageArray.push_back(
+                        {{"@odata.id",
+                          "/redfish/v1/Systems/system/Storage/1/Drive/" +
+                              objpath.substr(lastPos + 1)}});
+                }
+
+                asyncResp->res.jsonValue["Drives@odata.count"] =
+                    storageArray.size();
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
+            "/xyz/openbmc_project/inventory", int32_t(0),
+            std::array<const char *, 1>{
+                "xyz.openbmc_project.Inventory.Item.Drive"});
+    }
+};
+
+class Drive : public Node
+{
+  public:
+    Drive(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/system/Storage/1/Drive/<str>/",
+             std::string())
+    {
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+    }
+
+  private:
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        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) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "Drive mapper call error";
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                auto object = std::find_if(
+                    subtree.begin(), subtree.end(), [&driveId](auto &object) {
+                        const std::string &path = object.first;
+                        return boost::ends_with(path, "/" + driveId);
+                    });
+
+                if (object == subtree.end())
+                {
+                    messages::resourceNotFound(asyncResp->res, "Drive",
+                                               driveId);
+                    return;
+                }
+
+                const std::string &path = object->first;
+                const std::vector<
+                    std::pair<std::string, std::vector<std::string>>>
+                    &connectionNames = object->second;
+
+                asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive";
+                asyncResp->res.jsonValue["@odata.context"] =
+                    "/redfish/v1/$metadata#Drive.Drive";
+                asyncResp->res.jsonValue["@odata.id"] =
+                    "/redfish/v1/Systems/system/Storage/1/Drive/" + driveId;
+
+                if (connectionNames.size() != 1)
+                {
+                    BMCWEB_LOG_ERROR << "Connection size "
+                                     << connectionNames.size()
+                                     << ", greater than 1";
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                getMainChassisId(
+                    asyncResp, [](const std::string &chassisId,
+                                  std::shared_ptr<AsyncResp> aRsp) {
+                        aRsp->res.jsonValue["Links"]["Chassis"] = {
+                            {"@odata.id", "/redfish/v1/Chassis/" + chassisId}};
+                    });
+
+                const std::string &connectionName = connectionNames[0].first;
+                crow::connections::systemBus->async_method_call(
+                    [asyncResp,
+                     driveId](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;
+                            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;
+                                }
+                                asyncResp->res.jsonValue[propertyName] = *value;
+                            }
+                        }
+                        asyncResp->res.jsonValue["Name"] = driveId;
+                        asyncResp->res.jsonValue["Id"] = driveId;
+                    },
+                    connectionName, path, "org.freedesktop.DBus.Properties",
+                    "GetAll", "xyz.openbmc_project.Inventory.Decorator.Asset");
+
+                // default it to Enabled
+                asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
+
+                crow::connections::systemBus->async_method_call(
+                    [asyncResp, path](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)
+                        {
+                            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["Status"]["State"] =
+                                    "Disabled";
+                                return;
+                            }
+                        }
+
+                        // only populate if Enabled, assume enabled unless item
+                        // interface says otherwise
+                        auto health =
+                            std::make_shared<HealthPopulate>(asyncResp);
+                        health->inventory = std::vector<std::string>{path};
+
+                        health->populate();
+                    },
+                    connectionName, path, "org.freedesktop.DBus.Properties",
+                    "Get", "xyz.openbmc_project.Inventory.Item", "Present");
+            },
+            "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.Drive"});
+    }
+};
+} // namespace redfish
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 2edce0a..0724c85 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -1382,6 +1382,8 @@
             {"@odata.id", "/redfish/v1/Systems/system/Processors"}};
         res.jsonValue["Memory"] = {
             {"@odata.id", "/redfish/v1/Systems/system/Memory"}};
+        res.jsonValue["Storage"] = {
+            {"@odata.id", "/redfish/v1/Systems/system/Storage"}};
 
         // TODO Need to support ForceRestart.
         res.jsonValue["Actions"]["#ComputerSystem.Reset"] = {