POST EthernetInterfaceCollection for VLAN

With EthernetInterface 1.9.0, creation of VLAN interface is done by
POST EthernetInterfaceCollection. This patch implements the POST
handler to do so.

Tested:
* With valid RelatedInterfaces and VLANId provided, new VLAN interface
  is successfully created.
* Creating VLAN over another VLAN or non-existent interface returns
  error.
* Creating an existing VLAN returns ResourceAlreadyExists error.
* Invalid RelatedInterfaces links are rejected.

Change-Id: I6b1064193eccf7ec487b43139a73d9932b6eea84
Signed-off-by: Jiaqing Zhao <jiaqing.zhao@intel.com>
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/redfish-core/lib/ethernet.hpp b/redfish-core/lib/ethernet.hpp
index 74f544c..1d2c988 100644
--- a/redfish-core/lib/ethernet.hpp
+++ b/redfish-core/lib/ethernet.hpp
@@ -1749,6 +1749,50 @@
     messages::internalError(asyncResp->res);
 }
 
+inline void afterVlanCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                            const std::string& parentInterfaceUri,
+                            const std::string& vlanInterface,
+                            const boost::system::error_code& ec,
+                            const sdbusplus::message_t& m
+
+)
+{
+    if (ec)
+    {
+        const sd_bus_error* dbusError = m.get_error();
+        if (dbusError == nullptr)
+        {
+            messages::internalError(asyncResp->res);
+            return;
+        }
+        BMCWEB_LOG_DEBUG << "DBus error: " << dbusError->name;
+
+        if (std::string_view(
+                "xyz.openbmc_project.Common.Error.ResourceNotFound") ==
+            dbusError->name)
+        {
+            messages::propertyValueNotInList(
+                asyncResp->res, parentInterfaceUri,
+                "Links/RelatedInterfaces/0/@odata.id");
+            return;
+        }
+        if (std::string_view(
+                "xyz.openbmc_project.Common.Error.InvalidArgument") ==
+            dbusError->name)
+        {
+            messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface",
+                                            "Id", vlanInterface);
+            return;
+        }
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    const boost::urls::url vlanInterfaceUri = boost::urls::format(
+        "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", vlanInterface);
+    asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer());
+}
+
 inline void requestEthernetInterfacesRoutes(App& app)
 {
     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
@@ -1798,6 +1842,90 @@
         });
         });
 
+    BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
+        .privileges(redfish::privileges::postEthernetInterfaceCollection)
+        .methods(boost::beast::http::verb::post)(
+            [&app](const crow::Request& req,
+                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+        {
+            return;
+        }
+
+        bool vlanEnable = false;
+        uint32_t vlanId = 0;
+        nlohmann::json::array_t relatedInterfaces;
+
+        if (!json_util::readJsonPatch(req, asyncResp->res, "VLAN/VLANEnable",
+                                      vlanEnable, "VLAN/VLANId", vlanId,
+                                      "Links/RelatedInterfaces",
+                                      relatedInterfaces))
+        {
+            return;
+        }
+
+        if (relatedInterfaces.size() != 1)
+        {
+            messages::arraySizeTooLong(asyncResp->res,
+                                       "Links/RelatedInterfaces",
+                                       relatedInterfaces.size());
+            return;
+        }
+
+        std::string parentInterfaceUri;
+        if (!json_util::readJson(relatedInterfaces[0], asyncResp->res,
+                                 "@odata.id", parentInterfaceUri))
+        {
+            messages::propertyMissing(asyncResp->res,
+                                      "Links/RelatedInterfaces/0/@odata.id");
+            return;
+        }
+        BMCWEB_LOG_INFO << "Parent Interface URI: " << parentInterfaceUri;
+
+        boost::urls::result<boost::urls::url_view> parsedUri =
+            boost::urls::parse_relative_ref(parentInterfaceUri);
+        if (!parsedUri)
+        {
+            messages::propertyValueFormatError(
+                asyncResp->res, parentInterfaceUri,
+                "Links/RelatedInterfaces/0/@odata.id");
+            return;
+        }
+
+        std::string parentInterface;
+        if (!crow::utility::readUrlSegments(
+                *parsedUri, "redfish", "v1", "Managers", "bmc",
+                "EthernetInterfaces", std::ref(parentInterface)))
+        {
+            messages::propertyValueNotInList(
+                asyncResp->res, parentInterfaceUri,
+                "Links/RelatedInterfaces/0/@odata.id");
+            return;
+        }
+
+        if (!vlanEnable)
+        {
+            // In OpenBMC implementation, VLANEnable cannot be false on
+            // create
+            messages::propertyValueIncorrect(asyncResp->res, "VLAN/VLANEnable",
+                                             "false");
+            return;
+        }
+
+        std::string vlanInterface = parentInterface + "_" +
+                                    std::to_string(vlanId);
+        crow::connections::systemBus->async_method_call(
+            [asyncResp, parentInterfaceUri,
+             vlanInterface](const boost::system::error_code& ec,
+                            const sdbusplus::message_t& m) {
+            afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec,
+                            m);
+            },
+            "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
+            "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface,
+            vlanId);
+        });
+
     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/")
         .privileges(redfish::privileges::getEthernetInterface)
         .methods(boost::beast::http::verb::get)(