Initial implementation of processors and memory schema

This is an initial implementations of GET methods for
several schemas:

* ProcessorCollection
* Processor
* MemoryCollection
* Memory

It fetches data from Smbios service.

Tested:
* Compiance report
* Manual walk through selected schemas.

Change-Id: I11e00d0982248f2307e24b9a246ac60672dda8a1
Signed-off-by: Rapkiewicz, Pawel <pawel.rapkiewicz@intel.com>
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 8a8b656..8c3885f 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -17,6 +17,7 @@
 
 #include "../lib/account_service.hpp"
 #include "../lib/chassis.hpp"
+#include "../lib/cpudimm.hpp"
 #include "../lib/ethernet.hpp"
 #include "../lib/log_services.hpp"
 #include "../lib/managers.hpp"
@@ -85,6 +86,11 @@
 #endif // BMCWEB_ENABLE_REDFISH_RAW_PECI
 #endif // BMCWEB_ENABLE_REDFISH_CPU_LOG
 
+        nodes.emplace_back(std::make_unique<ProcessorCollection>(app));
+        nodes.emplace_back(std::make_unique<Processor>(app));
+        nodes.emplace_back(std::make_unique<MemoryCollection>(app));
+        nodes.emplace_back(std::make_unique<Memory>(app));
+
         nodes.emplace_back(std::make_unique<SystemsCollection>(app));
         nodes.emplace_back(std::make_unique<Systems>(app));
         nodes.emplace_back(std::make_unique<SystemActionsReset>(app));
diff --git a/redfish-core/lib/cpudimm.hpp b/redfish-core/lib/cpudimm.hpp
new file mode 100644
index 0000000..120b2b5
--- /dev/null
+++ b/redfish-core/lib/cpudimm.hpp
@@ -0,0 +1,513 @@
+/*
+// Copyright (c) 2018 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 <boost/container/flat_map.hpp>
+#include <node.hpp>
+#include <utils/json_utils.hpp>
+
+namespace redfish
+{
+
+void getResourceList(std::shared_ptr<AsyncResp> aResp, const std::string &name,
+                     const std::string &subclass,
+                     const std::string &collectionName)
+{
+    BMCWEB_LOG_DEBUG << "Get available system cpu/mem resources.";
+    crow::connections::systemBus->async_method_call(
+        [name, subclass, aResp{std::move(aResp)}](
+            const boost::system::error_code ec,
+            const boost::container::flat_map<
+                std::string, boost::container::flat_map<
+                                 std::string, std::vector<std::string>>>
+                &subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG << "DBUS response error";
+                messages::internalError(aResp->res);
+                return;
+            }
+            nlohmann::json &members = aResp->res.jsonValue["Members"];
+            members = nlohmann::json::array();
+
+            for (const auto &object : subtree)
+            {
+                auto iter = object.first.rfind("/");
+                if ((iter != std::string::npos) && (iter < object.first.size()))
+                {
+                    members.push_back(
+                        {{"@odata.id", "/redfish/v1/Systems/" + name + "/" +
+                                           subclass + "/" +
+                                           object.first.substr(iter + 1)}});
+                }
+            }
+            aResp->res.jsonValue["Members@odata.count"] = members.size();
+        },
+        "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>{collectionName.c_str()});
+}
+
+void getCpuDataByService(std::shared_ptr<AsyncResp> aResp,
+                         const std::string &name, const std::string &cpuId,
+                         const std::string &service, const std::string &objPath)
+{
+    BMCWEB_LOG_DEBUG << "Get available system cpu resources by service.";
+    crow::connections::systemBus->async_method_call(
+        [name, cpuId, aResp{std::move(aResp)}](
+            const boost::system::error_code ec,
+            const boost::container::flat_map<
+                std::string,
+                sdbusplus::message::variant<std::string, uint32_t, uint16_t>>
+                &properties) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG << "DBUS response error";
+                messages::internalError(aResp->res);
+
+                return;
+            }
+            aResp->res.jsonValue["Id"] = cpuId;
+            aResp->res.jsonValue["Name"] = "Processor";
+            const auto coresCountProperty =
+                properties.find("ProcessorCoreCount");
+            if (coresCountProperty == properties.end())
+            {
+                // Important property not in result
+                messages::internalError(aResp->res);
+                return;
+            }
+            const uint16_t *coresCount =
+                mapbox::getPtr<const uint16_t>(coresCountProperty->second);
+            if (coresCount == nullptr)
+            {
+                // Important property not in desired type
+                messages::internalError(aResp->res);
+                return;
+            }
+            if (*coresCount == 0)
+            {
+                // Slot is not populated, set status end return
+                aResp->res.jsonValue["Status"]["State"] = "Absent";
+                aResp->res.jsonValue["Status"]["Health"] = "OK";
+                // HTTP Code will be set up automatically, just return
+                return;
+            }
+
+            aResp->res.jsonValue["TotalCores"] = *coresCount;
+            aResp->res.jsonValue["Status"]["State"] = "Enabled";
+            aResp->res.jsonValue["Status"]["Health"] = "OK";
+
+            for (const auto &property : properties)
+            {
+                if (property.first == "ProcessorType")
+                {
+                    aResp->res.jsonValue["Name"] = property.second;
+                }
+                else if (property.first == "ProcessorManufacturer")
+                {
+                    aResp->res.jsonValue["Manufacturer"] = property.second;
+                    const std::string *value =
+                        mapbox::getPtr<const std::string>(property.second);
+                    if (value != nullptr)
+                    {
+                        // Otherwise would be unexpected.
+                        if (value->find("Intel") != std::string::npos)
+                        {
+                            aResp->res.jsonValue["ProcessorArchitecture"] =
+                                "x86";
+                            aResp->res.jsonValue["InstructionSet"] = "x86-64";
+                        }
+                    }
+                }
+                else if (property.first == "ProcessorMaxSpeed")
+                {
+                    aResp->res.jsonValue["MaxSpeedMHz"] = property.second;
+                }
+                else if (property.first == "ProcessorThreadCount")
+                {
+                    aResp->res.jsonValue["TotalThreads"] = property.second;
+                }
+                else if (property.first == "ProcessorVersion")
+                {
+                    aResp->res.jsonValue["Model"] = property.second;
+                }
+            }
+        },
+        service, objPath, "org.freedesktop.DBus.Properties", "GetAll", "");
+}
+
+void getCpuData(std::shared_ptr<AsyncResp> aResp, const std::string &name,
+                const std::string &cpuId)
+{
+    BMCWEB_LOG_DEBUG << "Get available system cpu resources.";
+    crow::connections::systemBus->async_method_call(
+        [name, cpuId, aResp{std::move(aResp)}](
+            const boost::system::error_code ec,
+            const boost::container::flat_map<
+                std::string, boost::container::flat_map<
+                                 std::string, std::vector<std::string>>>
+                &subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG << "DBUS response error";
+                messages::internalError(aResp->res);
+                return;
+            }
+            for (const auto &object : subtree)
+            {
+                if (boost::ends_with(object.first, cpuId))
+                {
+                    for (const auto &service : object.second)
+                    {
+                        getCpuDataByService(aResp, name, cpuId, service.first,
+                                            object.first);
+                        return;
+                    }
+                }
+            }
+            // Object not found
+            messages::resourceNotFound(aResp->res, "Processor", cpuId);
+            return;
+        },
+        "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.Cpu"});
+};
+
+void getDimmDataByService(std::shared_ptr<AsyncResp> aResp,
+                          const std::string &name, const std::string &dimmId,
+                          const std::string &service,
+                          const std::string &objPath)
+{
+    BMCWEB_LOG_DEBUG << "Get available system components.";
+    crow::connections::systemBus->async_method_call(
+        [name, dimmId, aResp{std::move(aResp)}](
+            const boost::system::error_code ec,
+            const boost::container::flat_map<
+                std::string,
+                sdbusplus::message::variant<std::string, uint32_t, uint16_t>>
+                &properties) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG << "DBUS response error";
+                messages::internalError(aResp->res);
+
+                return;
+            }
+            aResp->res.jsonValue["Id"] = dimmId;
+            aResp->res.jsonValue["Name"] = "DIMM Slot";
+
+            const auto memorySizeProperty = properties.find("MemorySizeInKB");
+            if (memorySizeProperty == properties.end())
+            {
+                // Important property not in result
+                messages::internalError(aResp->res);
+
+                return;
+            }
+            const uint32_t *memorySize =
+                mapbox::getPtr<const uint32_t>(memorySizeProperty->second);
+            if (memorySize == nullptr)
+            {
+                // Important property not in desired type
+                messages::internalError(aResp->res);
+
+                return;
+            }
+            if (*memorySize == 0)
+            {
+                // Slot is not populated, set status end return
+                aResp->res.jsonValue["Status"]["State"] = "Absent";
+                aResp->res.jsonValue["Status"]["Health"] = "OK";
+                // HTTP Code will be set up automatically, just return
+                return;
+            }
+            aResp->res.jsonValue["CapacityMiB"] = (*memorySize >> 10);
+            aResp->res.jsonValue["Status"]["State"] = "Enabled";
+            aResp->res.jsonValue["Status"]["Health"] = "OK";
+
+            for (const auto &property : properties)
+            {
+                if (property.first == "MemoryDataWidth")
+                {
+                    aResp->res.jsonValue["DataWidthBits"] = property.second;
+                }
+                else if (property.first == "MemoryType")
+                {
+                    const auto *value =
+                        mapbox::getPtr<const std::string>(property.second);
+                    if (value != nullptr)
+                    {
+                        aResp->res.jsonValue["MemoryDeviceType"] = *value;
+                        if (boost::starts_with(*value, "DDR"))
+                        {
+                            aResp->res.jsonValue["MemoryType"] = "DRAM";
+                        }
+                    }
+                }
+            }
+        },
+        service, objPath, "org.freedesktop.DBus.Properties", "GetAll", "");
+}
+
+void getDimmData(std::shared_ptr<AsyncResp> aResp, const std::string &name,
+                 const std::string &dimmId)
+{
+    BMCWEB_LOG_DEBUG << "Get available system dimm resources.";
+    crow::connections::systemBus->async_method_call(
+        [name, dimmId, aResp{std::move(aResp)}](
+            const boost::system::error_code ec,
+            const boost::container::flat_map<
+                std::string, boost::container::flat_map<
+                                 std::string, std::vector<std::string>>>
+                &subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG << "DBUS response error";
+                messages::internalError(aResp->res);
+
+                return;
+            }
+            for (const auto &object : subtree)
+            {
+                if (boost::ends_with(object.first, dimmId))
+                {
+                    for (const auto &service : object.second)
+                    {
+                        getDimmDataByService(aResp, name, dimmId, service.first,
+                                             object.first);
+                        return;
+                    }
+                }
+            }
+            // Object not found
+            messages::resourceNotFound(aResp->res, "Memory", dimmId);
+            return;
+        },
+        "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.Dimm"});
+};
+
+class ProcessorCollection : public Node
+{
+  public:
+    /*
+     * Default Constructor
+     */
+    ProcessorCollection(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/<str>/Processors/", std::string())
+    {
+
+        Node::json["@odata.type"] = "#ProcessorCollection.ProcessorCollection";
+        Node::json["Name"] = "Processor Collection";
+        Node::json["@odata.context"] =
+            "/redfish/v1/$metadata#ProcessorCollection.ProcessorCollection";
+        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:
+    /**
+     * Functions triggers appropriate requests on DBus
+     */
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        // Check if there is required param, truly entering this shall be
+        // impossible
+        if (params.size() != 1)
+        {
+            messages::internalError(res);
+            res.end();
+            return;
+        }
+        const std::string &name = params[0];
+
+        res.jsonValue = Node::json;
+        res.jsonValue["@odata.id"] =
+            "/redfish/v1/Systems/" + name + "/Processors/";
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        getResourceList(asyncResp, name, "Processors",
+                        "xyz.openbmc_project.Inventory.Item.Cpu");
+    }
+};
+
+class Processor : public Node
+{
+  public:
+    /*
+     * Default Constructor
+     */
+    Processor(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/<str>/Processors/<str>/", std::string(),
+             std::string())
+    {
+
+        Node::json["@odata.type"] = "#Processor.v1_1_0.Processor";
+        Node::json["@odata.context"] =
+            "/redfish/v1/$metadata#Processor.Processor";
+        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:
+    /**
+     * Functions triggers appropriate requests on DBus
+     */
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        // Check if there is required param, truly entering this shall be
+        // impossible
+        if (params.size() != 2)
+        {
+            messages::internalError(res);
+
+            res.end();
+            return;
+        }
+        const std::string &name = params[0];
+        const std::string &cpuId = params[1];
+
+        res.jsonValue = Node::json;
+        res.jsonValue["@odata.id"] =
+            "/redfish/v1/Systems/" + name + "/Processors/" + cpuId;
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        getCpuData(asyncResp, name, cpuId);
+    }
+};
+
+class MemoryCollection : public Node
+{
+  public:
+    /*
+     * Default Constructor
+     */
+    MemoryCollection(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/<str>/Memory/", std::string())
+    {
+
+        Node::json["@odata.type"] = "#MemoryCollection.MemoryCollection";
+        Node::json["Name"] = "Memory Module Collection";
+        Node::json["@odata.context"] =
+            "/redfish/v1/$metadata#MemoryCollection.MemoryCollection";
+        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:
+    /**
+     * Functions triggers appropriate requests on DBus
+     */
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        // Check if there is required param, truly entering this shall be
+        // impossible
+        if (params.size() != 1)
+        {
+            messages::internalError(res);
+
+            res.end();
+            return;
+        }
+        const std::string &name = params[0];
+
+        res.jsonValue = Node::json;
+        res.jsonValue["@odata.id"] = "/redfish/v1/Systems/" + name + "/Memory/";
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        getResourceList(asyncResp, name, "Memory",
+                        "xyz.openbmc_project.Inventory.Item.Dimm");
+    }
+};
+
+class Memory : public Node
+{
+  public:
+    /*
+     * Default Constructor
+     */
+    Memory(CrowApp &app) :
+        Node(app, "/redfish/v1/Systems/<str>/Memory/<str>/", std::string(),
+             std::string())
+    {
+
+        Node::json["@odata.type"] = "#Memory.v1_2_0.Memory";
+        Node::json["@odata.context"] = "/redfish/v1/$metadata#Memory.Memory";
+        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:
+    /**
+     * Functions triggers appropriate requests on DBus
+     */
+    void doGet(crow::Response &res, const crow::Request &req,
+               const std::vector<std::string> &params) override
+    {
+        // Check if there is required param, truly entering this shall be
+        // impossible
+        if (params.size() != 2)
+        {
+            messages::internalError(res);
+            res.end();
+            return;
+        }
+        const std::string &name = params[0];
+        const std::string &dimmId = params[1];
+
+        res.jsonValue = Node::json;
+        res.jsonValue["@odata.id"] =
+            "/redfish/v1/Systems/" + name + "/Memory/" + dimmId;
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        getDimmData(asyncResp, name, dimmId);
+    }
+};
+
+} // namespace redfish
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 027c276..4ab4eb9 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -701,6 +701,10 @@
         res.jsonValue = Node::json;
         res.jsonValue["@odata.id"] = "/redfish/v1/Systems/" + name;
 
+        res.jsonValue["Processors"] = {
+            {"@odata.id", "/redfish/v1/Systems/" + name + "/Processors"}};
+        res.jsonValue["Memory"] = {
+            {"@odata.id", "/redfish/v1/Systems/" + name + "/Memory"}};
         // TODO Need to support ForceRestart.
         res.jsonValue["Actions"]["#ComputerSystem.Reset"] = {
             {"target",