Adding Chassis and ChassisCollection Schemas to Redfish

This commit:
* removes previous redfish_v1 Chassis implementation
* Adds Chassis and ChassisCollection implementation as Node way
* Adds Chassis Provider class for retrieving data from EntityManager

It was tested:
* Wolfpass run, to see if previous functionality was not broken
* Service Validator, which did not unveil any regression, and did
verified that implemented schemas are complient.

Change-Id: I75a9545a0abd8b85d6ce72329c523fc076affc28
Signed-off-by: Rapkiewicz, Pawel <pawel.rapkiewicz@intel.com>
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Signed-off-by: Rapkiewicz, Pawel <pawel.rapkiewicz@intel.com>
diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp
new file mode 100644
index 0000000..8f88eb1
--- /dev/null
+++ b/redfish-core/lib/chassis.hpp
@@ -0,0 +1,278 @@
+/*
+// 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 "node.hpp"
+#include <boost/container/flat_map.hpp>
+
+namespace redfish {
+
+/**
+ * DBus types primitives for several generic DBus interfaces
+ * TODO(Pawel) consider move this to separate file into boost::dbus
+ */
+using ManagedObjectsType = std::vector<
+    std::pair<dbus::object_path,
+              std::vector<std::pair<
+                  std::string,
+                  std::vector<std::pair<std::string, dbus::dbus_variant>>>>>>;
+
+using PropertiesType =
+    boost::container::flat_map<std::string, dbus::dbus_variant>;
+
+/**
+ * OnDemandChassisProvider
+ * Chassis provider class that retrieves data directly from dbus, before seting
+ * it into JSON output. This does not cache any data.
+ *
+ * Class can be a good example on how to scale different data providing
+ * solutions to produce single schema output.
+ *
+ * TODO(Pawel)
+ * This perhaps shall be different file, which has to be chosen on compile time
+ * depending on OEM needs
+ */
+class OnDemandChassisProvider {
+ public:
+  /**
+   * Function that retrieves all properties for given Chassis Object from
+   * EntityManager
+   * @param res_name a chassis resource name to query on DBus
+   * @param callback a function that shall be called to convert Dbus output into
+   * JSON
+   */
+  template <typename CallbackFunc>
+  void get_chassis_data(const std::string &res_name, CallbackFunc &&callback) {
+    crow::connections::system_bus->async_method_call(
+        [callback{std::move(callback)}](
+            const boost::system::error_code error_code,
+            const PropertiesType &properties) {
+          // Callback requires flat_map<string, string> so prepare one.
+          boost::container::flat_map<std::string, std::string> output;
+          if (error_code) {
+            // Something wrong on DBus, the error_code is not important at this
+            // moment, just return success=false, and empty output. Since size
+            // of map may vary depending on Chassis type, an empty output could
+            // not be treated same way as error.
+            callback(false, output);
+            return;
+          }
+          for (const std::pair<const char *, const char *> &p :
+               std::array<std::pair<const char *, const char *>, 5>{
+                   {{"name", "Name"},
+                    {"manufacturer", "Manufacturer"},
+                    {"model", "Model"},
+                    {"part_number", "PartNumber"},
+                    {"serial_number", "SerialNumber"}}}) {
+            PropertiesType::const_iterator it = properties.find(p.first);
+            if (it != properties.end()) {
+              const std::string *s = boost::get<std::string>(&it->second);
+              if (s != nullptr) {
+                output[p.second] = *s;
+              }
+            }
+          }
+          // Callback with success, and hopefully data.
+          callback(true, output);
+        },
+        {"xyz.openbmc_project.EntityManager",
+         "/xyz/openbmc_project/Inventory/Item/Chassis/" + res_name,
+         "org.freedesktop.DBus.Properties", "GetAll"},
+        "xyz.openbmc_project.Configuration.Chassis");
+  }
+
+  /**
+   * Function that retrieves all Chassis available through EntityManager.
+   * @param callback a function that shall be called to convert Dbus output into
+   * JSON.
+   */
+  template <typename CallbackFunc>
+  void get_chassis_list(CallbackFunc &&callback) {
+    crow::connections::system_bus->async_method_call(
+        [callback{std::move(callback)}](
+            const boost::system::error_code error_code,
+            const ManagedObjectsType &resp) {
+          // Callback requires vector<string> to retrieve all available chassis
+          // list.
+          std::vector<std::string> chassis_list;
+          if (error_code) {
+            // Something wrong on DBus, the error_code is not important at this
+            // moment, just return success=false, and empty output. Since size
+            // of vector may vary depending on information from Entity Manager,
+            // and empty output could not be treated same way as error.
+            callback(false, chassis_list);
+            return;
+          }
+
+          // Iterate over all retrieved ObjectPaths.
+          for (auto &objpath : resp) {
+            // And all interfaces available for certain ObjectPath.
+            for (auto &interface : objpath.second) {
+              // If interface is xyz.openbmc_project.Configuration.Chassis, this
+              // is Chassis.
+              if (interface.first ==
+                  "xyz.openbmc_project.Configuration.Chassis") {
+                // Cut out everyting until last "/", ...
+                const std::string &chassis_id = objpath.first.value;
+                std::size_t last_pos = chassis_id.rfind("/");
+                if (last_pos != std::string::npos) {
+                  // and put it into output vector.
+                  chassis_list.emplace_back(chassis_id.substr(last_pos + 1));
+                }
+              }
+            }
+          }
+          // Finally make a callback with usefull data
+          callback(true, chassis_list);
+        },
+        {"xyz.openbmc_project.EntityManager",
+         "/xyz/openbmc_project/Inventory/Item/Chassis",
+         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"});
+  };
+};
+
+/**
+ * ChassisCollection derived class for delivering Chassis Collection Schema
+ */
+class ChassisCollection : public Node {
+ public:
+  template <typename CrowApp>
+  ChassisCollection(CrowApp &app) : Node(app, "/redfish/v1/Chassis/") {
+    Node::json["@odata.type"] = "#ChassisCollection.ChassisCollection";
+    Node::json["@odata.id"] = "/redfish/v1/Chassis";
+    Node::json["@odata.context"] =
+        "/redfish/v1/$metadata#ChassisCollection.ChassisCollection";
+    Node::json["Name"] = "Chassis Collection";
+
+    entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
+                        {crow::HTTPMethod::HEAD, {{"Login"}}},
+                        {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}},
+                        {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}},
+                        {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}},
+                        {crow::HTTPMethod::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 {
+    // Get chassis list, and call the below callback for JSON preparation
+    chassis_provider.get_chassis_list(
+        [&](const bool &success, const std::vector<std::string> &output) {
+          if (success) {
+            // ... prepare json array with appropriate @odata.id links
+            nlohmann::json chassis_array = nlohmann::json::array();
+            for (const std::string &chassis_item : output) {
+              chassis_array.push_back(
+                  {{"@odata.id", "/redfish/v1/Chassis/" + chassis_item}});
+            }
+            // Then attach members, count size and return,
+            Node::json["Members"] = chassis_array;
+            Node::json["Members@odata.count"] = chassis_array.size();
+            res.json_value = Node::json;
+          } else {
+            // ... otherwise, return INTERNALL ERROR
+            res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
+          }
+          res.end();
+        });
+  }
+
+  // Chassis Provider object
+  // TODO(Pawel) consider move it to singleton
+  OnDemandChassisProvider chassis_provider;
+};
+
+/**
+ * Chassis override class for delivering Chassis Schema
+ */
+class Chassis : public Node {
+ public:
+  /*
+   * Default Constructor
+   */
+  template <typename CrowApp>
+  Chassis(CrowApp &app)
+      : Node(app, "/redfish/v1/Chassis/<str>/", std::string()) {
+    Node::json["@odata.type"] = "#Chassis.v1_4_0.Chassis";
+    Node::json["@odata.id"] = "/redfish/v1/Chassis";
+    Node::json["@odata.context"] = "/redfish/v1/$metadata#Chassis.Chassis";
+    Node::json["Name"] = "Chassis Collection";
+
+    entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
+                        {crow::HTTPMethod::HEAD, {{"Login"}}},
+                        {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}},
+                        {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}},
+                        {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}},
+                        {crow::HTTPMethod::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) {
+      res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR);
+      res.end();
+      return;
+    }
+    const std::string &chassis_id = params[0];
+    // Get certain Chassis Data, and call the below callback for JSON
+    // preparation lambda requires everything as reference, and chassis_id,
+    // which is local by copy
+    chassis_provider.get_chassis_data(
+        chassis_id,
+        [&, chassis_id](const bool &success,
+                        const boost::container::flat_map<std::string,
+                                                         std::string> &output) {
+          // Create JSON copy based on Node::json, this is to avoid possible
+          // race condition
+          nlohmann::json json_response(Node::json);
+          // If success...
+          if (success) {
+            // prepare all the schema required fields.
+            json_response["@odata.id"] = "/redfish/v1/Chassis/" + chassis_id;
+            // also the one from dbus
+            for (const std::pair<std::string, std::string> &chassis_item :
+                 output) {
+              json_response[chassis_item.first] = chassis_item.second;
+            }
+            json_response["Id"] = chassis_id;
+
+            // prepare respond, and send
+            res.json_value = json_response;
+          } else {
+            // ... otherwise return error
+            // TODO(Pawel)consider distinguish between non existing object, and
+            // other errors
+            res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
+          }
+          res.end();
+        });
+  }
+
+  // Chassis Provider object
+  // TODO(Pawel) consider move it to singleton
+  OnDemandChassisProvider chassis_provider;
+};
+
+}  // namespace redfish