Implement VlanNetworkInterface Schema DELETE and PATCH methods

Does not introduce regression. Tested on HW platform and x86 dev system.

Change-Id: I1e8894367e664de4900f7a528d1ce8ee4d66ac1c
Signed-off-by: Kowalski, Kamil <kamil.kowalski@intel.com>
diff --git a/redfish-core/lib/ethernet.hpp b/redfish-core/lib/ethernet.hpp
index a3b1701..6c2bf6c 100644
--- a/redfish-core/lib/ethernet.hpp
+++ b/redfish-core/lib/ethernet.hpp
@@ -286,8 +286,9 @@
    * @return None.
    */
   template <typename CallbackFunc>
-  void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId,
-                    CallbackFunc &&callback) {
+  static void changeVlanId(const std::string &ifaceId,
+                           const uint32_t &inputVlanId,
+                           CallbackFunc &&callback) {
     crow::connections::system_bus->async_method_call(
         callback, "xyz.openbmc_project.Network",
         std::string("/xyz/openbmc_project/network/") + ifaceId,
@@ -491,7 +492,7 @@
    * @return None.
    */
   template <typename CallbackFunc>
-  void disableVlan(const std::string &ifaceId, CallbackFunc &&callback) {
+  static void disableVlan(const std::string &ifaceId, CallbackFunc &&callback) {
     crow::connections::system_bus->async_method_call(
         callback, "xyz.openbmc_project.Network",
         std::string("/xyz/openbmc_project/network/") + ifaceId,
@@ -826,27 +827,36 @@
         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
   }
 
- private:
-  void handleVlanPatch(const std::string &ifaceId, const nlohmann::json &input,
-                       const EthernetInterfaceData &eth_data,
-                       const std::shared_ptr<AsyncResp> &asyncResp) {
+  // TODO(kkowalsk) Find a suitable class/namespace for this
+  static void handleVlanPatch(const std::string &ifaceId,
+                              const nlohmann::json &input,
+                              const EthernetInterfaceData &eth_data,
+                              const std::string &pathPrefix,
+                              const std::shared_ptr<AsyncResp> &asyncResp) {
     if (!input.is_object()) {
       messages::addMessageToJson(
           asyncResp->res.json_value,
-          messages::propertyValueTypeError(input.dump(), "VLAN"), "/VLAN");
+          messages::propertyValueTypeError(input.dump(), "VLAN"), pathPrefix);
       return;
     }
 
+    const std::string pathStart = (pathPrefix == "/") ? "" : pathPrefix;
+    nlohmann::json &paramsJson =
+        (pathPrefix == "/")
+            ? asyncResp->res.json_value
+            : asyncResp->res.json_value[nlohmann::json_pointer<nlohmann::json>(
+                  pathPrefix)];
     bool inputVlanEnabled;
     uint64_t inputVlanId;
+
     json_util::Result inputVlanEnabledState = json_util::getBool(
         "VLANEnable", input, inputVlanEnabled,
         static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
-        asyncResp->res.json_value, std::string("/VLAN/VLANEnable"));
+        asyncResp->res.json_value, std::string(pathStart + "/VLANEnable"));
     json_util::Result inputVlanIdState = json_util::getUnsigned(
         "VLANId", input, inputVlanId,
         static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
-        asyncResp->res.json_value, std::string("/VLAN/VLANId"));
+        asyncResp->res.json_value, std::string(pathStart + "/VLANId"));
     bool inputInvalid = false;
 
     // Do not proceed if fields in VLAN object were of wrong type
@@ -857,31 +867,13 @@
 
     // Verify input
     if (eth_data.vlan_id == nullptr) {
-      // VLAN is currently disabled. User can only create/enable it. Change of
-      // VLANId is prohibited, and disable request (VLANEnabled == false) will
-      // not have any effect.
-      if (inputVlanEnabledState == json_util::Result::SUCCESS &&
-          inputVlanEnabled == true) {
-        // Creation requested, user should also provide ID for new VLAN
-        if (inputVlanIdState != json_util::Result::SUCCESS) {
-          messages::addMessageToJson(asyncResp->res.json_value,
-                                     messages::propertyMissing("VLANId"),
-                                     "/VLAN");
-          inputInvalid = true;
-        }
-      } else if (inputVlanIdState == json_util::Result::SUCCESS) {
-        // VLAN is disabled, but user requested modification. This is not valid.
-        messages::addMessageToJson(
-            asyncResp->res.json_value,
-            messages::actionParameterNotSupported("VLANId", "change VLAN Id"),
-            "/VLAN");
+      // This interface is not a VLAN. Cannot do anything with it
+      // TODO(kkowalsk) Change this message
+      messages::addMessageToJson(asyncResp->res.json_value,
+                                 messages::propertyMissing("VLANEnable"),
+                                 pathPrefix);
 
-        messages::addMessageToJson(asyncResp->res.json_value,
-                                   messages::propertyMissing("VLANEnable"),
-                                   "/VLAN");
-
-        inputInvalid = true;
-      }
+      inputInvalid = true;
     } else {
       // Load actual data into field values if they were not provided
       if (inputVlanEnabledState == json_util::Result::NOT_EXIST) {
@@ -898,45 +890,37 @@
       return;
     }
 
-    auto vlanEnabledAfterOperation =
-        [asyncResp](const boost::system::error_code ec) {
-          if (ec) {
-            messages::addMessageToJson(asyncResp->res.json_value,
-                                       messages::internalError(), "/VLAN");
-          } else {
-            asyncResp->res.json_value["VLAN"]["VLANEnable"] = true;
-          }
-        };
-
-    if (eth_data.vlan_id == nullptr) {
-      if (inputVlanEnabled == true) {
-        ethernet_provider.createVlan(ifaceId, inputVlanId,
-                                     std::move(vlanEnabledAfterOperation));
-        asyncResp->res.json_value["VLAN"]["VLANId"] = inputVlanId;
-      }
-    } else {
-      // VLAN is configured on the interface
-      if (inputVlanEnabled == true && inputVlanId != *eth_data.vlan_id) {
-        // Change VLAN Id
-        asyncResp->res.json_value["VLAN"]["VLANId"] = inputVlanId;
-        ethernet_provider.changeVlanId(ifaceId,
-                                       static_cast<uint32_t>(inputVlanId),
-                                       std::move(vlanEnabledAfterOperation));
-      } else if (inputVlanEnabled == false) {
-        // Disable VLAN
-        ethernet_provider.disableVlan(
-            ifaceId, [asyncResp](const boost::system::error_code ec) {
-              if (ec) {
-                messages::addMessageToJson(asyncResp->res.json_value,
-                                           messages::internalError(), "/VLAN");
-              } else {
-                asyncResp->res.json_value["VLAN"]["VLANEnable"] = false;
-              }
-            });
-      }
+    // VLAN is configured on the interface
+    if (inputVlanEnabled == true && inputVlanId != *eth_data.vlan_id) {
+      // Change VLAN Id
+      paramsJson["VLANId"] = inputVlanId;
+      OnDemandEthernetProvider::changeVlanId(
+          ifaceId, static_cast<uint32_t>(inputVlanId),
+          [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
+              const boost::system::error_code ec) {
+            if (ec) {
+              messages::addMessageToJson(asyncResp->res.json_value,
+                                         messages::internalError(), pathPrefix);
+            } else {
+              paramsJson["VLANEnable"] = true;
+            }
+          });
+    } else if (inputVlanEnabled == false) {
+      // Disable VLAN
+      OnDemandEthernetProvider::disableVlan(
+          ifaceId, [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
+                       const boost::system::error_code ec) {
+            if (ec) {
+              messages::addMessageToJson(asyncResp->res.json_value,
+                                         messages::internalError(), pathPrefix);
+            } else {
+              paramsJson["VLANEnable"] = false;
+            }
+          });
     }
   }
 
+ private:
   void handleHostnamePatch(const nlohmann::json &input,
                            const EthernetInterfaceData &eth_data,
                            const std::shared_ptr<AsyncResp> &asyncResp) {
@@ -1260,6 +1244,9 @@
             // ... otherwise return error
             // TODO(Pawel)consider distinguish between non existing object, and
             // other errors
+            messages::addMessageToErrorJson(
+                res.json_value,
+                messages::resourceNotFound("EthernetInterface", iface_id));
             res.result(boost::beast::http::status::not_found);
           }
           res.end();
@@ -1311,7 +1298,7 @@
           for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
                ++propertyIt) {
             if (propertyIt.key() == "VLAN") {
-              handleVlanPatch(iface_id, propertyIt.value(), eth_data,
+              handleVlanPatch(iface_id, propertyIt.value(), eth_data, "/VLAN",
                               asyncResp);
             } else if (propertyIt.key() == "HostName") {
               handleHostnamePatch(propertyIt.value(), eth_data, asyncResp);
@@ -1347,4 +1334,424 @@
   OnDemandEthernetProvider ethernet_provider;
 };
 
+class VlanNetworkInterfaceCollection;
+
+/**
+ * VlanNetworkInterface derived class for delivering VLANNetworkInterface Schema
+ */
+class VlanNetworkInterface : public Node {
+ public:
+  /*
+   * Default Constructor
+   */
+  template <typename CrowApp>
+  // TODO(Pawel) Remove line from below, where we assume that there is only one
+  // manager called openbmc This shall be generic, but requires to update
+  // GetSubroutes method
+  VlanNetworkInterface(CrowApp &app)
+      : Node(app,
+             "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/"
+             "<str>",
+             std::string(), std::string()) {
+    Node::json["@odata.type"] =
+        "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
+    Node::json["@odata.context"] =
+        "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface";
+    Node::json["Name"] = "VLAN Network Interface";
+
+    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:
+  nlohmann::json parseInterfaceData(
+      const std::string &parent_iface_id, const std::string &iface_id,
+      const EthernetInterfaceData &eth_data,
+      const std::vector<IPv4AddressData> &ipv4_data) {
+    // Copy JSON object to avoid race condition
+    nlohmann::json json_response(Node::json);
+
+    // Fill out obvious data...
+    json_response["Id"] = iface_id;
+    json_response["@odata.id"] =
+        "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + parent_iface_id +
+        "/VLANs/" + iface_id;
+
+    json_response["VLANEnable"] = true;
+    json_response["VLANId"] = *eth_data.vlan_id;
+
+    return json_response;
+  }
+
+  bool verifyNames(crow::response &res, const std::string &parent,
+                   const std::string &iface) {
+    if (!boost::starts_with(iface, parent + "_")) {
+      messages::addMessageToErrorJson(
+          res.json_value,
+          messages::resourceNotFound("VLAN Network Interface", iface));
+      res.result(boost::beast::http::status::bad_request);
+      res.end();
+
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  /**
+   * Functions triggers appropriate requests on DBus
+   */
+  void doGet(crow::response &res, const crow::request &req,
+             const std::vector<std::string> &params) override {
+    // TODO(Pawel) this shall be parametrized call (two params) to get
+    // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
+    // Check if there is required param, truly entering this shall be
+    // impossible.
+    if (params.size() != 2) {
+      res.result(boost::beast::http::status::internal_server_error);
+      res.end();
+      return;
+    }
+
+    const std::string &parent_iface_id = params[0];
+    const std::string &iface_id = params[1];
+
+    if (!verifyNames(res, parent_iface_id, iface_id)) {
+      return;
+    }
+
+    // Get single eth interface data, and call the below callback for JSON
+    // preparation
+    ethernet_provider.getEthernetIfaceData(
+        iface_id,
+        [&, parent_iface_id, iface_id](
+            const bool &success, const EthernetInterfaceData &eth_data,
+            const std::vector<IPv4AddressData> &ipv4_data) {
+          if (success && eth_data.vlan_id != nullptr) {
+            res.json_value = parseInterfaceData(parent_iface_id, iface_id,
+                                                eth_data, ipv4_data);
+          } else {
+            // ... otherwise return error
+            // TODO(Pawel)consider distinguish between non existing object,
+            // and
+            // other errors
+            res.result(boost::beast::http::status::not_found);
+          }
+          res.end();
+        });
+  }
+
+  void doPatch(crow::response &res, const crow::request &req,
+               const std::vector<std::string> &params) override {
+    if (params.size() != 2) {
+      res.result(boost::beast::http::status::internal_server_error);
+      res.end();
+      return;
+    }
+
+    const std::string &parent_iface_id = params[0];
+    const std::string &iface_id = params[1];
+
+    if (!verifyNames(res, parent_iface_id, iface_id)) {
+      return;
+    }
+
+    nlohmann::json patchReq;
+
+    if (!json_util::processJsonFromRequest(res, req, patchReq)) {
+      return;
+    }
+
+    // Get single eth interface data, and call the below callback for JSON
+    // preparation
+    ethernet_provider.getEthernetIfaceData(
+        iface_id,
+        [&, parent_iface_id, iface_id, patchReq = std::move(patchReq) ](
+            const bool &success, const EthernetInterfaceData &eth_data,
+            const std::vector<IPv4AddressData> &ipv4_data) {
+          if (!success) {
+            // ... otherwise return error
+            // TODO(Pawel)consider distinguish between non existing object,
+            // and
+            // other errors
+            res.result(boost::beast::http::status::not_found);
+            res.end();
+
+            return;
+          }
+
+          res.json_value = parseInterfaceData(parent_iface_id, iface_id,
+                                              eth_data, ipv4_data);
+
+          std::shared_ptr<AsyncResp> asyncResp =
+              std::make_shared<AsyncResp>(res);
+
+          for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
+               ++propertyIt) {
+            if (propertyIt.key() != "VLANEnable" &&
+                propertyIt.key() != "VLANId") {
+              auto fieldInJsonIt = res.json_value.find(propertyIt.key());
+
+              if (fieldInJsonIt == res.json_value.end()) {
+                // Field not in scope of defined fields
+                messages::addMessageToJsonRoot(
+                    res.json_value,
+                    messages::propertyUnknown(propertyIt.key()));
+              } else if (*fieldInJsonIt != *propertyIt) {
+                // User attempted to modify non-writable field
+                messages::addMessageToJsonRoot(
+                    res.json_value,
+                    messages::propertyNotWritable(propertyIt.key()));
+              }
+            }
+          }
+
+          EthernetInterface::handleVlanPatch(iface_id, patchReq, eth_data, "/",
+                                             asyncResp);
+        });
+  }
+
+  void doDelete(crow::response &res, const crow::request &req,
+                const std::vector<std::string> &params) override {
+    if (params.size() != 2) {
+      res.result(boost::beast::http::status::internal_server_error);
+      res.end();
+      return;
+    }
+
+    const std::string &parent_iface_id = params[0];
+    const std::string &iface_id = params[1];
+
+    if (!verifyNames(res, parent_iface_id, iface_id)) {
+      return;
+    }
+
+    // Get single eth interface data, and call the below callback for JSON
+    // preparation
+    ethernet_provider.getEthernetIfaceData(
+        iface_id,
+        [&, parent_iface_id, iface_id](
+            const bool &success, const EthernetInterfaceData &eth_data,
+            const std::vector<IPv4AddressData> &ipv4_data) {
+          if (success && eth_data.vlan_id != nullptr) {
+            res.json_value = parseInterfaceData(parent_iface_id, iface_id,
+                                                eth_data, ipv4_data);
+
+            // Disable VLAN
+            OnDemandEthernetProvider::disableVlan(
+                iface_id, [&](const boost::system::error_code ec) {
+                  if (ec) {
+                    res.json_value = nlohmann::json::object();
+                    messages::addMessageToErrorJson(res.json_value,
+                                                    messages::internalError());
+                    res.result(
+                        boost::beast::http::status::internal_server_error);
+                  }
+                  res.end();
+                });
+          } else {
+            // ... otherwise return error
+            // TODO(Pawel)consider distinguish between non existing object,
+            // and
+            // other errors
+
+            res.result(boost::beast::http::status::not_found);
+            res.end();
+          }
+        });
+  }
+
+  /**
+   * This allows VlanNetworkInterfaceCollection to reuse this class' doGet
+   * method, to maintain consistency of returned data, as Collection's doPost
+   * should return data for created member which should match member's doGet
+   * result in 100%.
+   */
+  friend VlanNetworkInterfaceCollection;
+
+  // Ethernet Provider object
+  // TODO(Pawel) consider move it to singleton
+  OnDemandEthernetProvider ethernet_provider;
+};
+
+/**
+ * VlanNetworkInterfaceCollection derived class for delivering
+ * VLANNetworkInterface Collection Schema
+ */
+class VlanNetworkInterfaceCollection : public Node {
+ public:
+  template <typename CrowApp>
+  // TODO(Pawel) Remove line from below, where we assume that there is only one
+  // manager called openbmc This shall be generic, but requires to update
+  // GetSubroutes method
+  VlanNetworkInterfaceCollection(CrowApp &app)
+      : Node(app,
+             "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/",
+             std::string()),
+        memberVlan(app) {
+    Node::json["@odata.type"] =
+        "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
+    Node::json["@odata.context"] =
+        "/redfish/v1/$metadata"
+        "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
+    Node::json["Name"] = "VLAN Network Interface Collection";
+
+    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 {
+    if (params.size() != 1) {
+      // This means there is a problem with the router
+      res.result(boost::beast::http::status::internal_server_error);
+      res.end();
+
+      return;
+    }
+
+    // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
+    // any Manager, not only hardcoded 'openbmc'.
+    std::string manager_id = "openbmc";
+    std::string rootInterfaceName = params[0];
+
+    // Get eth interface list, and call the below callback for JSON preparation
+    ethernet_provider.getEthernetIfaceList([
+          &, manager_id{std::move(manager_id)},
+          rootInterfaceName{std::move(rootInterfaceName)}
+    ](const bool &success, const std::vector<std::string> &iface_list) {
+      if (success) {
+        bool rootInterfaceFound = false;
+        nlohmann::json iface_array = nlohmann::json::array();
+
+        for (const std::string &iface_item : iface_list) {
+          if (iface_item == rootInterfaceName) {
+            rootInterfaceFound = true;
+          } else if (boost::starts_with(iface_item, rootInterfaceName + "_")) {
+            iface_array.push_back(
+                {{"@odata.id", "/redfish/v1/Managers/" + manager_id +
+                                   "/EthernetInterfaces/" + rootInterfaceName +
+                                   "/VLANs/" + iface_item}});
+          }
+        }
+
+        if (rootInterfaceFound) {
+          Node::json["Members"] = iface_array;
+          Node::json["Members@odata.count"] = iface_array.size();
+          Node::json["@odata.id"] = "/redfish/v1/Managers/" + manager_id +
+                                    "/EthernetInterfaces/" + rootInterfaceName +
+                                    "/VLANs";
+          res.json_value = Node::json;
+        } else {
+          messages::addMessageToErrorJson(
+              res.json_value, messages::resourceNotFound("EthernetInterface",
+                                                         rootInterfaceName));
+          res.result(boost::beast::http::status::not_found);
+          res.end();
+        }
+      } else {
+        // No success, best what we can do is return INTERNALL ERROR
+        res.result(boost::beast::http::status::internal_server_error);
+      }
+      res.end();
+    });
+  }
+
+  void doPost(crow::response &res, const crow::request &req,
+              const std::vector<std::string> &params) override {
+    if (params.size() != 1) {
+      // This means there is a problem with the router
+      res.result(boost::beast::http::status::internal_server_error);
+      res.end();
+      return;
+    }
+
+    nlohmann::json postReq;
+
+    if (!json_util::processJsonFromRequest(res, req, postReq)) {
+      return;
+    }
+
+    // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
+    // any Manager, not only hardcoded 'openbmc'.
+    std::string manager_id = "openbmc";
+    std::string rootInterfaceName = params[0];
+    uint64_t vlanId;
+    bool errorDetected;
+
+    if (json_util::getUnsigned(
+            "VLANId", postReq, vlanId,
+            static_cast<uint8_t>(json_util::MessageSetting::MISSING) |
+                static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
+            res.json_value, "/VLANId") != json_util::Result::SUCCESS) {
+      res.end();
+      return;
+    }
+
+    // Get eth interface list, and call the below callback for JSON preparation
+    ethernet_provider.getEthernetIfaceList([
+          &, manager_id{std::move(manager_id)},
+          rootInterfaceName{std::move(rootInterfaceName)}
+    ](const bool &success, const std::vector<std::string> &iface_list) {
+      if (success) {
+        bool rootInterfaceFound = false;
+
+        for (const std::string &iface_item : iface_list) {
+          if (iface_item == rootInterfaceName) {
+            rootInterfaceFound = true;
+            break;
+          }
+        }
+
+        if (rootInterfaceFound) {
+          ethernet_provider.createVlan(
+              rootInterfaceName, vlanId,
+              [&, vlanId, rootInterfaceName,
+               req{std::move(req)} ](const boost::system::error_code ec) {
+                if (ec) {
+                  messages::addMessageToErrorJson(res.json_value,
+                                                  messages::internalError());
+                  res.end();
+                } else {
+                  memberVlan.doGet(
+                      res, req,
+                      {rootInterfaceName,
+                       rootInterfaceName + "_" + std::to_string(vlanId)});
+                }
+              });
+        } else {
+          messages::addMessageToErrorJson(
+              res.json_value, messages::resourceNotFound("EthernetInterface",
+                                                         rootInterfaceName));
+          res.result(boost::beast::http::status::not_found);
+          res.end();
+        }
+      } else {
+        // No success, best what we can do is return INTERNALL ERROR
+        res.result(boost::beast::http::status::internal_server_error);
+        res.end();
+      }
+    });
+  }
+
+  // Ethernet Provider object
+  // TODO(Pawel) consider move it to singleton
+  OnDemandEthernetProvider ethernet_provider;
+  VlanNetworkInterface memberVlan;
+};
+
 }  // namespace redfish