ethernet_interface: Merge VLAN into EthernetInterface

This simplifies the ethernet interface as it codepended on the vlan
interface to make VLANs. It also removes the naming dependency and
tracks VLANs by adapter type and parent indexes.

Change-Id: I8db09cc2474472e6d4a06c6be5cf0440ee48d132
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index 021c126..537b7ad 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -139,6 +139,15 @@
 
     updateInfo(info);
 
+    if (info.vlan_id)
+    {
+        if (!info.parent_idx)
+        {
+            std::runtime_error("Missing parent link");
+        }
+        vlan.emplace(bus, this->objPath.c_str(), info, *this, emitSignal);
+    }
+
     // Emit deferred signal.
     if (emitSignal)
     {
@@ -372,48 +381,6 @@
     manager.reloadConfigs();
 }
 
-void EthernetInterface::deleteVLANFromSystem(stdplus::zstring_view interface)
-{
-    const auto& confDir = manager.getConfDir();
-    auto networkFile = config::pathForIntfConf(confDir, interface);
-    auto deviceFile = config::pathForIntfDev(confDir, interface);
-
-    // delete the vlan network file
-    std::error_code ec;
-    std::filesystem::remove(networkFile, ec);
-    std::filesystem::remove(deviceFile, ec);
-
-    // TODO  systemd doesn't delete the virtual network interface
-    // even after deleting all the related configuartion.
-    // https://github.com/systemd/systemd/issues/6600
-    try
-    {
-        deleteInterface(interface);
-    }
-    catch (const InternalFailure& e)
-    {
-        commit<InternalFailure>();
-    }
-}
-
-void EthernetInterface::deleteVLANObject(stdplus::zstring_view interface)
-{
-    auto it = vlanInterfaces.find(interface);
-    if (it == vlanInterfaces.end())
-    {
-        log<level::ERR>("DeleteVLANObject:Unable to find the object",
-                        entry("INTERFACE=%s", interface.c_str()));
-        return;
-    }
-
-    deleteVLANFromSystem(interface);
-    // delete the interface
-    vlanInterfaces.erase(it);
-
-    writeConfigurationFile();
-    manager.reloadConfigs();
-}
-
 std::string EthernetInterface::generateObjectPath(
     IP::Protocol addressType, std::string_view ipAddress, uint8_t prefixLength,
     IP::AddressOrigin origin) const
@@ -762,39 +729,15 @@
     return servers;
 }
 
-std::string EthernetInterface::vlanIntfName(uint16_t id) const
-{
-    return fmt::format(FMT_COMPILE("{}.{}"), interfaceName(), id);
-}
-
-void EthernetInterface::loadVLAN(std::string_view objRoot, uint16_t id,
-                                 system::InterfaceInfo&& info)
-{
-    config::Parser config(
-        config::pathForIntfConf(manager.getConfDir(), *info.name));
-    auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
-        bus, manager, info, objRoot, config, id, *this);
-
-    // Fetch the ip address from the system
-    // and create the dbus object.
-    vlanIntf->createIPAddressObjects();
-    vlanIntf->createStaticNeighborObjects();
-    vlanIntf->loadNameServers(config);
-    vlanIntf->loadNTPServers(config);
-
-    this->vlanInterfaces.emplace(std::move(*info.name), std::move(vlanIntf));
-}
-
 ObjectPath EthernetInterface::createVLAN(uint16_t id)
 {
-    auto vlanInterfaceName = vlanIntfName(id);
-    if (this->vlanInterfaces.find(vlanInterfaceName) !=
-        this->vlanInterfaces.end())
+    auto intfName = fmt::format(FMT_COMPILE("{}.{}"), interfaceName(), id);
+    auto idStr = std::to_string(id);
+    if (manager.interfaces.find(intfName) != manager.interfaces.end())
     {
         log<level::ERR>("VLAN already exists", entry("VLANID=%u", id));
-        elog<InvalidArgument>(
-            Argument::ARGUMENT_NAME("VLANId"),
-            Argument::ARGUMENT_VALUE(std::to_string(id).c_str()));
+        elog<InvalidArgument>(Argument::ARGUMENT_NAME("VLANId"),
+                              Argument::ARGUMENT_VALUE(idStr.c_str()));
     }
 
     auto objRoot = std::string_view(objPath).substr(0, objPath.rfind('/'));
@@ -807,23 +750,29 @@
     auto info = system::InterfaceInfo{
         .idx = 0, // TODO: Query the correct value after creation
         .flags = 0,
-        .name = vlanInterfaceName,
+        .name = intfName,
         .mac = std::move(mac),
         .mtu = mtu(),
+        .parent_idx = ifIdx,
+        .vlan_id = id,
     };
 
     // Pass the parents nicEnabled property, so that the child
     // VLAN interface can inherit.
-    auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
-        bus, manager, info, objRoot, config::Parser(), id, *this, /*emit=*/true,
+    auto vlanIntf = std::make_unique<EthernetInterface>(
+        bus, manager, info, objRoot, config::Parser(), /*emit=*/true,
         nicEnabled());
     ObjectPath ret = vlanIntf->objPath;
 
-    // write the device file for the vlan interface.
-    vlanIntf->writeDeviceFile();
+    manager.interfaces.emplace(intfName, std::move(vlanIntf));
 
-    this->vlanInterfaces.emplace(std::move(vlanInterfaceName),
-                                 std::move(vlanIntf));
+    // write the device file for the vlan interface.
+    config::Parser config;
+    auto& netdev = config.map["NetDev"].emplace_back();
+    netdev["Name"].emplace_back(intfName);
+    netdev["Kind"].emplace_back("vlan");
+    config.map["VLAN"].emplace_back()["Id"].emplace_back(std::move(idStr));
+    config.writeFile(config::pathForIntfDev(manager.getConfDir(), intfName));
 
     writeConfigurationFile();
     manager.reloadConfigs();
@@ -857,11 +806,6 @@
 
 void EthernetInterface::writeConfigurationFile()
 {
-    for (const auto& intf : vlanInterfaces)
-    {
-        intf.second->writeConfigurationFile();
-    }
-
     config::Parser config;
     config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName());
     {
@@ -891,10 +835,12 @@
                                              : (dhcp6() ? "ipv6" : "false"));
         {
             auto& vlans = network["VLAN"];
-            for (const auto& intf : vlanInterfaces)
+            for (const auto& [_, intf] : manager.interfaces)
             {
-                vlans.emplace_back(
-                    intf.second->EthernetInterface::interfaceName());
+                if (intf->vlan && intf->vlan->parentIdx == ifIdx)
+                {
+                    vlans.emplace_back(intf->interfaceName());
+                }
             }
         }
         {
@@ -980,6 +926,11 @@
 
 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
 {
+    if (vlan)
+    {
+        log<level::ERR>("Tried to set MAC address on VLAN");
+        elog<InternalFailure>();
+    }
 #ifdef PERSIST_MAC
     ether_addr newMAC;
     try
@@ -1009,9 +960,12 @@
     if (newMAC != oldMAC)
     {
         // Update everything that depends on the MAC value
-        for (const auto& [name, intf] : vlanInterfaces)
+        for (const auto& [_, intf] : manager.interfaces)
         {
-            intf->MacAddressIntf::macAddress(validMAC);
+            if (intf->vlan && intf->vlan->parentIdx == ifIdx)
+            {
+                intf->MacAddressIntf::macAddress(validMAC);
+            }
         }
         MacAddressIntf::macAddress(validMAC);
 
@@ -1096,5 +1050,47 @@
 
     return gw;
 }
+
+EthernetInterface::VlanProperties::VlanProperties(
+    sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
+    const system::InterfaceInfo& info, EthernetInterface& eth,
+    bool emitSignal) :
+    VlanIfaces(bus, objPath.c_str(),
+               emitSignal ? VlanIfaces::action::defer_emit
+                          : VlanIfaces::action::emit_no_signals),
+    parentIdx(*info.parent_idx), eth(eth)
+{
+    VlanIntf::id(*info.vlan_id);
+    if (emitSignal)
+    {
+        this->emit_object_added();
+    }
+}
+
+void EthernetInterface::VlanProperties::delete_()
+{
+    auto intf = eth.interfaceName();
+
+    // Remove all configs for the current interface
+    const auto& confDir = eth.manager.getConfDir();
+    std::error_code ec;
+    std::filesystem::remove(config::pathForIntfConf(confDir, intf), ec);
+    std::filesystem::remove(config::pathForIntfDev(confDir, intf), ec);
+
+    // Write an updated parent interface since it has a VLAN entry
+    for (const auto& [_, intf] : eth.manager.interfaces)
+    {
+        if (intf->ifIdx == parentIdx)
+        {
+            intf->writeConfigurationFile();
+        }
+    }
+
+    // We need to forcibly delete the interface as systemd does not
+    deleteInterface(intf);
+
+    eth.manager.interfaces.erase(intf);
+}
+
 } // namespace network
 } // namespace phosphor
diff --git a/src/ethernet_interface.hpp b/src/ethernet_interface.hpp
index 4db338e..462937f 100644
--- a/src/ethernet_interface.hpp
+++ b/src/ethernet_interface.hpp
@@ -14,6 +14,8 @@
 #include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
 #include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
 #include <xyz/openbmc_project/Network/MACAddress/server.hpp>
+#include <xyz/openbmc_project/Network/VLAN/server.hpp>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
 
 namespace phosphor
 {
@@ -27,6 +29,12 @@
     sdbusplus::xyz::openbmc_project::Network::Neighbor::server::CreateStatic,
     sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll>;
 
+using VlanIfaces = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Object::server::Delete,
+    sdbusplus::xyz::openbmc_project::Network::server::VLAN>;
+
+using VlanIntf = sdbusplus::xyz::openbmc_project::Network::server::VLAN;
+
 using IP = sdbusplus::xyz::openbmc_project::Network::server::IP;
 
 using EthernetInterfaceIntf =
@@ -40,8 +48,7 @@
 class Manager;
 
 class TestEthernetInterface;
-
-class VlanInterface;
+class TestNetworkManager;
 
 namespace config
 {
@@ -73,6 +80,7 @@
      *  @param[in] info - Interface information.
      *  @param[in] objRoot - Path to attach at.
      *  @param[in] config - The parsed configuation file.
+     *  @param[in] vlan - The id of the vlan if configured
      *  @param[in] emitSignal - true if the object added signal needs to be
      *                          send.
      *  @param[in] enabled - Override the lookup of nicEnabled
@@ -119,12 +127,6 @@
      */
     void deleteStaticNeighborObject(std::string_view ipAddress);
 
-    /* @brief delete the vlan dbus object of the given interface.
-     *        Also deletes the device file and the network file.
-     * @param[in] interface - VLAN Interface.
-     */
-    void deleteVLANObject(stdplus::zstring_view interface);
-
     /* @brief creates the dbus object(IPaddres) given in the address list.
      * @param[in] addrs - address list for which dbus objects needs
      *                    to create.
@@ -204,13 +206,6 @@
      */
     ObjectPath createVLAN(uint16_t id);
 
-    /** @brief load the vlan info from the system
-     *         and creates the ip address dbus objects.
-     *  @param[in] vlanID- VLAN identifier.
-     */
-    void loadVLAN(std::string_view objRoot, uint16_t vlanID,
-                  system::InterfaceInfo&& info);
-
     /** @brief write the network conf file with the in-memory objects.
      */
     void writeConfigurationFile();
@@ -239,11 +234,6 @@
     using EthernetInterfaceIntf::defaultGateway6;
 
   protected:
-    /* @brief delete the vlan interface from system.
-     * @param[in] interface - vlan Interface.
-     */
-    void deleteVLANFromSystem(stdplus::zstring_view interface);
-
     /** @brief construct the ip address dbus object path.
      *  @param[in] addressType - Type of ip address.
      *  @param[in] ipAddress - IP address.
@@ -283,16 +273,25 @@
     /** @brief Persistent map of Neighbor dbus objects and their names */
     string_umap<std::unique_ptr<Neighbor>> staticNeighbors;
 
-    /** @brief Persistent map of VLAN interface dbus objects and their names */
-    string_umap<std::unique_ptr<VlanInterface>> vlanInterfaces;
-
     /** @brief Dbus object path */
     std::string objPath;
 
     /** @brief Interface index */
     unsigned ifIdx;
 
+    struct VlanProperties : VlanIfaces
+    {
+        VlanProperties(sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
+                       const system::InterfaceInfo& info,
+                       EthernetInterface& eth, bool emitSignal = true);
+        void delete_() override;
+        unsigned parentIdx;
+        EthernetInterface& eth;
+    };
+    std::optional<VlanProperties> vlan;
+
     friend class TestEthernetInterface;
+    friend class TestNetworkManager;
 
   private:
     EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
@@ -316,8 +315,6 @@
      *  @returns true/false value if the NIC is enabled
      */
     bool queryNicEnabled() const;
-
-    std::string vlanIntfName(uint16_t id) const;
 };
 
 } // namespace network
diff --git a/src/meson.build b/src/meson.build
index 926d6bd..79fa0b7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -59,7 +59,6 @@
   'routing_table.cpp',
   'config_parser.cpp',
   'dhcp_configuration.cpp',
-  'vlan_interface.cpp',
   'rtnetlink_server.cpp',
   'dns_updater.cpp',
   implicit_include_directories: false,
diff --git a/src/network_manager.cpp b/src/network_manager.cpp
index 0032525..c79c395 100644
--- a/src/network_manager.cpp
+++ b/src/network_manager.cpp
@@ -2,11 +2,11 @@
 
 #include "network_manager.hpp"
 
+#include "config_parser.hpp"
 #include "ipaddress.hpp"
 #include "system_queries.hpp"
 #include "types.hpp"
 
-#include <charconv>
 #include <filesystem>
 #include <fstream>
 #include <phosphor-logging/elog-errors.hpp>
@@ -63,41 +63,10 @@
     interfaces.clear();
     for (auto& interface : system::getInterfaces())
     {
-        // interface can be of vlan type or normal ethernet interface.
-        // vlan interface looks like "interface.vlanid",so here by looking
-        // at the interface name we decide that we need
-        // to create the vlaninterface or normal physical interface.
-        if (const auto index = interface.name->find(".");
-            index != std::string::npos)
-        {
-            // it is vlan interface
-            auto sv = std::string_view(*interface.name);
-            auto interfaceName = sv.substr(0, index);
-            auto vlanStr = sv.substr(index + 1);
-            uint16_t vlanId;
-            auto res = std::from_chars(vlanStr.begin(), vlanStr.end(), vlanId);
-            if (res.ec != std::errc() || res.ptr != vlanStr.end())
-            {
-                auto msg = fmt::format("Invalid VLAN: {}", vlanStr);
-                log<level::ERR>(msg.c_str());
-                continue;
-            }
-            auto it = interfaces.find(interfaceName);
-            if (it == interfaces.end())
-            {
-                auto msg = fmt::format("Missing interface({}) for VLAN({}): {}",
-                                       interfaceName, vlanId, *interface.name);
-                log<level::ERR>(msg.c_str());
-                continue;
-            }
-            it->second->loadVLAN(objectPath, vlanId, std::move(interface));
-            continue;
-        }
-
         config::Parser config(
             config::pathForIntfConf(confDir, *interface.name));
-        auto intf = std::make_unique<phosphor::network::EthernetInterface>(
-            bus, *this, interface, objectPath, config);
+        auto intf = std::make_unique<EthernetInterface>(bus, *this, interface,
+                                                        objectPath, config);
         intf->createIPAddressObjects();
         intf->createStaticNeighborObjects();
         intf->loadNameServers(config);
diff --git a/src/network_manager.hpp b/src/network_manager.hpp
index 09c3d55..ffac8ba 100644
--- a/src/network_manager.hpp
+++ b/src/network_manager.hpp
@@ -4,7 +4,6 @@
 #include "routing_table.hpp"
 #include "system_configuration.hpp"
 #include "types.hpp"
-#include "vlan_interface.hpp"
 #include "xyz/openbmc_project/Network/VLAN/Create/server.hpp"
 
 #include <filesystem>
@@ -125,14 +124,9 @@
      */
     void doReloadConfigs();
 
-    /** @brief Get the interfaces owned by the manager
-     *
-     * @return Interfaces reference.
+    /** @brief Persistent map of EthernetInterface dbus objects and their names
      */
-    inline const auto& getInterfaces() const
-    {
-        return interfaces;
-    }
+    string_umap<std::unique_ptr<EthernetInterface>> interfaces;
 
     /** @brief Get the routing table owned by the manager
      *
@@ -156,10 +150,6 @@
     /** @brief Persistent sdbusplus DBus bus connection. */
     sdbusplus::bus_t& bus;
 
-    /** @brief Persistent map of EthernetInterface dbus objects and their names
-     */
-    string_umap<std::unique_ptr<EthernetInterface>> interfaces;
-
     /** @brief BMC network reset - resets network configuration for BMC. */
     void reset() override;
 
diff --git a/src/system_queries.cpp b/src/system_queries.cpp
index 0152f4a..f0222ec 100644
--- a/src/system_queries.cpp
+++ b/src/system_queries.cpp
@@ -125,6 +125,47 @@
     getIFSock().ioctl(SIOCSIFFLAGS, &ifr);
 }
 
+static void parseVlanInfo(InterfaceInfo& info, std::string_view msg)
+{
+    if (msg.data() == nullptr)
+    {
+        throw std::runtime_error("Missing VLAN data");
+    }
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        switch (hdr.rta_type)
+        {
+            case IFLA_VLAN_ID:
+                info.vlan_id.emplace(stdplus::raw::copyFrom<uint16_t>(data));
+                break;
+        }
+    }
+}
+
+static void parseLinkInfo(InterfaceInfo& info, std::string_view msg)
+{
+    std::string_view submsg;
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        switch (hdr.rta_type)
+        {
+            case IFLA_INFO_KIND:
+                data.remove_suffix(1);
+                info.kind.emplace(data);
+                break;
+            case IFLA_INFO_DATA:
+                submsg = data;
+                break;
+        }
+    }
+    if (info.kind == "vlan"sv)
+    {
+        parseVlanInfo(info, submsg);
+    }
+}
+
 InterfaceInfo detail::parseInterface(const nlmsghdr& hdr, std::string_view msg)
 {
     if (hdr.nlmsg_type != RTM_NEWLINK)
@@ -138,22 +179,28 @@
     while (!msg.empty())
     {
         auto [hdr, data] = netlink::extractRtAttr(msg);
-        if (hdr.rta_type == IFLA_IFNAME)
+        switch (hdr.rta_type)
         {
-            ret.name.emplace(data.begin(), data.end() - 1);
-        }
-        else if (hdr.rta_type == IFLA_ADDRESS)
-        {
-            if (data.size() != sizeof(ether_addr))
-            {
-                // Some interfaces have IP addresses for their LLADDR
-                continue;
-            }
-            ret.mac.emplace(stdplus::raw::copyFrom<ether_addr>(data));
-        }
-        else if (hdr.rta_type == IFLA_MTU)
-        {
-            ret.mtu.emplace(stdplus::raw::copyFrom<unsigned>(data));
+            case IFLA_IFNAME:
+                ret.name.emplace(data.begin(), data.end() - 1);
+                break;
+            case IFLA_ADDRESS:
+                if (data.size() != sizeof(ether_addr))
+                {
+                    // Some interfaces have IP addresses for their LLADDR
+                    break;
+                }
+                ret.mac.emplace(stdplus::raw::copyFrom<ether_addr>(data));
+                break;
+            case IFLA_MTU:
+                ret.mtu.emplace(stdplus::raw::copyFrom<unsigned>(data));
+                break;
+            case IFLA_LINK:
+                ret.parent_idx.emplace(stdplus::raw::copyFrom<unsigned>(data));
+                break;
+            case IFLA_LINKINFO:
+                parseLinkInfo(ret, data);
+                break;
         }
     }
     return ret;
diff --git a/src/system_queries.hpp b/src/system_queries.hpp
index 3bbb021..32d54cf 100644
--- a/src/system_queries.hpp
+++ b/src/system_queries.hpp
@@ -40,11 +40,16 @@
     std::optional<std::string> name = std::nullopt;
     std::optional<ether_addr> mac = std::nullopt;
     std::optional<unsigned> mtu = std::nullopt;
+    std::optional<unsigned> parent_idx = std::nullopt;
+    std::optional<std::string> kind = std::nullopt;
+    std::optional<uint16_t> vlan_id = std::nullopt;
 
     constexpr bool operator==(const InterfaceInfo& rhs) const noexcept
     {
         return idx == rhs.idx && flags == rhs.flags && name == rhs.name &&
-               mac == rhs.mac && mtu == rhs.mtu;
+               mac == rhs.mac && mtu == rhs.mtu &&
+               parent_idx == rhs.parent_idx && kind == rhs.kind &&
+               vlan_id == rhs.vlan_id;
     }
 };
 
diff --git a/src/vlan_interface.cpp b/src/vlan_interface.cpp
deleted file mode 100644
index cb086c7..0000000
--- a/src/vlan_interface.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#include "vlan_interface.hpp"
-
-#include "config_parser.hpp"
-#include "ethernet_interface.hpp"
-#include "network_manager.hpp"
-#include "system_queries.hpp"
-
-#include <phosphor-logging/elog-errors.hpp>
-#include <phosphor-logging/log.hpp>
-#include <string>
-#include <xyz/openbmc_project/Common/error.hpp>
-
-namespace phosphor
-{
-namespace network
-{
-
-using namespace phosphor::logging;
-using namespace sdbusplus::xyz::openbmc_project::Common::Error;
-
-VlanInterface::VlanInterface(sdbusplus::bus_t& bus, Manager& manager,
-                             const system::InterfaceInfo& info,
-                             std::string_view objRoot,
-                             const config::Parser& config, uint16_t vlanID,
-                             EthernetInterface& parent, bool emitSignal,
-                             std::optional<bool> enabled) :
-    EthernetInterface(bus, manager, info, objRoot, config, emitSignal, enabled),
-    DeleteIface(bus, objPath.c_str()), VlanIface(bus, objPath.c_str()),
-    parentInterface(parent)
-{
-    id(vlanID);
-    emit_object_added();
-}
-
-std::string VlanInterface::macAddress(std::string)
-{
-    log<level::ERR>("Tried to set MAC address on VLAN");
-    elog<InternalFailure>();
-}
-
-void VlanInterface::writeDeviceFile()
-{
-    config::Parser config;
-    auto& netdev = config.map["NetDev"].emplace_back();
-    netdev["Name"].emplace_back(EthernetInterface::interfaceName());
-    netdev["Kind"].emplace_back("vlan");
-    config.map["VLAN"].emplace_back()["Id"].emplace_back(std::to_string(id()));
-    config.writeFile(config::pathForIntfDev(
-        manager.getConfDir(), EthernetInterface::interfaceName()));
-}
-
-void VlanInterface::delete_()
-{
-    parentInterface.deleteVLANObject(EthernetInterface::interfaceName());
-}
-
-} // namespace network
-} // namespace phosphor
diff --git a/src/vlan_interface.hpp b/src/vlan_interface.hpp
deleted file mode 100644
index 7cdb61d..0000000
--- a/src/vlan_interface.hpp
+++ /dev/null
@@ -1,88 +0,0 @@
-#pragma once
-
-#include "config_parser.hpp"
-#include "ethernet_interface.hpp"
-#include "types.hpp"
-#include "xyz/openbmc_project/Network/VLAN/server.hpp"
-
-#include <sdbusplus/bus.hpp>
-#include <sdbusplus/server/object.hpp>
-#include <xyz/openbmc_project/Object/Delete/server.hpp>
-
-namespace phosphor
-{
-namespace network
-{
-
-class EthernetInterface;
-class Manager;
-namespace system
-{
-struct InterfaceInfo;
-}
-
-using DeleteIface = sdbusplus::xyz::openbmc_project::Object::server::Delete;
-using VlanIface = sdbusplus::xyz::openbmc_project::Network::server::VLAN;
-
-/** @class VlanInterface
- *  @brief OpenBMC vlan Interface implementation.
- *  @details A concrete implementation for the vlan interface
- */
-class VlanInterface : public EthernetInterface,
-                      public DeleteIface,
-                      public VlanIface
-{
-  public:
-    VlanInterface() = delete;
-    VlanInterface(const VlanInterface&) = delete;
-    VlanInterface& operator=(const VlanInterface&) = delete;
-    VlanInterface(VlanInterface&&) = delete;
-    VlanInterface& operator=(VlanInterface&&) = delete;
-    virtual ~VlanInterface() = default;
-
-    /** @brief Constructor to put object onto bus at a dbus path.
-     *  @param[in] bus - Bus to attach to.
-     *  @param[in] manager - network manager object.
-     *  @param[in] info - Interface information.
-     *  @param[in] objRoot - Path to attach at.
-     *  @param[in] config - The parsed configuation file.
-     *  @param[in] vlanID - vlan identifier.
-     *  @param[in] parent - ethernet interface object.
-     *  @param[in] emitSignal - true if the object added signal needs to be
-     *                          send.
-     *  @param[in] enabled - Override the lookup of nicEnabled
-     *
-     *  This constructor is called during loading the VLAN Interface
-     */
-    VlanInterface(sdbusplus::bus_t& bus, Manager& manager,
-                  const system::InterfaceInfo& info, std::string_view objRoot,
-                  const config::Parser& config, uint16_t vlanID,
-                  EthernetInterface& parent, bool emitSignal = true,
-                  std::optional<bool> enabled = std::nullopt);
-
-    /** @brief Delete this d-bus object.
-     */
-    void delete_() override;
-
-    /** @brief sets the MAC address.
-     *  @param[in] value - MAC address which needs to be set on the system.
-     *  @returns macAddress of the interface or throws an error.
-     */
-    std::string macAddress(std::string value) override;
-
-    /** @brief writes the device configuration.
-               systemd reads this configuration file
-               and creates the vlan interface.*/
-    void writeDeviceFile();
-
-  private:
-    /** @brief VLAN Identifier. */
-    using VlanIface::id;
-
-    EthernetInterface& parentInterface;
-
-    friend class TestVlanInterface;
-};
-
-} // namespace network
-} // namespace phosphor
diff --git a/test/meson.build b/test/meson.build
index 651f542..3f84156 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -54,7 +54,6 @@
   'system_queries',
   'types',
   'util',
-  'vlan_interface',
 ]
 
 run_with_tmp = find_program('run_with_tmp', native: true)
diff --git a/test/test_network_manager.cpp b/test/test_network_manager.cpp
index 459bacf..7e3e211 100644
--- a/test/test_network_manager.cpp
+++ b/test/test_network_manager.cpp
@@ -1,3 +1,4 @@
+#include "config_parser.hpp"
 #include "mock_network_manager.hpp"
 #include "mock_syscall.hpp"
 
@@ -6,6 +7,7 @@
 #include <netinet/in.h>
 #include <stdlib.h>
 
+#include <filesystem>
 #include <sdbusplus/bus.hpp>
 #include <stdplus/gtest/tmp.hpp>
 
@@ -21,7 +23,7 @@
 
 class TestNetworkManager : public stdplus::gtest::TestWithTmp
 {
-  public:
+  protected:
     sdbusplus::bus_t bus;
     MockManager manager;
     TestNetworkManager() :
@@ -35,13 +37,18 @@
     {
         manager.createInterfaces();
     }
+
+    void deleteVLAN(std::string_view ifname)
+    {
+        manager.interfaces.find(ifname)->second->vlan->delete_();
+    }
 };
 
 // getifaddrs will not return any interface
 TEST_F(TestNetworkManager, NoInterface)
 {
     createInterfaces();
-    EXPECT_TRUE(manager.getInterfaces().empty());
+    EXPECT_TRUE(manager.interfaces.empty());
 }
 // getifaddrs returns single interface.
 TEST_F(TestNetworkManager, WithSingleInterface)
@@ -52,7 +59,7 @@
     // Now create the interfaces which will call the mocked getifaddrs
     // which returns the above interface detail.
     createInterfaces();
-    EXPECT_THAT(manager.getInterfaces(), UnorderedElementsAre(Key("igb1")));
+    EXPECT_THAT(manager.interfaces, UnorderedElementsAre(Key("igb1")));
 }
 
 // getifaddrs returns two interfaces.
@@ -62,8 +69,34 @@
     system::mock_addIF({.idx = 2, .flags = 0, .name = "igb1"});
 
     createInterfaces();
-    EXPECT_THAT(manager.getInterfaces(),
+    EXPECT_THAT(manager.interfaces,
                 UnorderedElementsAre(Key("igb0"), Key("igb1")));
 }
+
+TEST_F(TestNetworkManager, WithVLAN)
+{
+    EXPECT_THROW(manager.vlan("", 8000), std::exception);
+    EXPECT_THROW(manager.vlan("", 0), std::exception);
+    EXPECT_THROW(manager.vlan("eth0", 2), std::exception);
+
+    system::mock_addIF({.idx = 1, .flags = 0, .name = "eth0"});
+    manager.createInterfaces();
+    EXPECT_NO_THROW(manager.vlan("eth0", 2));
+    EXPECT_NO_THROW(manager.vlan("eth0", 4094));
+    EXPECT_THAT(
+        manager.interfaces,
+        UnorderedElementsAre(Key("eth0"), Key("eth0.2"), Key("eth0.4094")));
+    auto netdev1 = config::pathForIntfDev(CaseTmpDir(), "eth0.2");
+    auto netdev2 = config::pathForIntfDev(CaseTmpDir(), "eth0.4094");
+    EXPECT_TRUE(std::filesystem::is_regular_file(netdev1));
+    EXPECT_TRUE(std::filesystem::is_regular_file(netdev2));
+
+    deleteVLAN("eth0.2");
+    EXPECT_THAT(manager.interfaces,
+                UnorderedElementsAre(Key("eth0"), Key("eth0.4094")));
+    EXPECT_FALSE(std::filesystem::is_regular_file(netdev1));
+    EXPECT_TRUE(std::filesystem::is_regular_file(netdev2));
+}
+
 } // namespace network
 } // namespace phosphor
diff --git a/test/test_vlan_interface.cpp b/test/test_vlan_interface.cpp
deleted file mode 100644
index d5b6989..0000000
--- a/test/test_vlan_interface.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-#include "config_parser.hpp"
-#include "ipaddress.hpp"
-#include "mock_network_manager.hpp"
-#include "mock_syscall.hpp"
-#include "system_queries.hpp"
-#include "vlan_interface.hpp"
-
-#include <arpa/inet.h>
-#include <net/if.h>
-#include <netinet/in.h>
-
-#include <filesystem>
-#include <sdbusplus/bus.hpp>
-#include <stdplus/gtest/tmp.hpp>
-
-#include <gtest/gtest.h>
-
-namespace phosphor
-{
-namespace network
-{
-
-namespace fs = std::filesystem;
-using std::literals::string_view_literals::operator""sv;
-
-class TestVlanInterface : public stdplus::gtest::TestWithTmp
-{
-  public:
-    sdbusplus::bus_t bus;
-    std::string confDir;
-    MockManager manager;
-    EthernetInterface interface;
-    TestVlanInterface() :
-        bus(sdbusplus::bus::new_default()), confDir(CaseTmpDir()),
-        manager(bus, "/xyz/openbmc_test/network", confDir),
-        interface(makeInterface(bus, manager))
-
-    {
-    }
-
-    static EthernetInterface makeInterface(sdbusplus::bus_t& bus,
-                                           MockManager& manager)
-    {
-        system::mock_clear();
-        system::InterfaceInfo info{.idx = 1, .flags = 0, .name = "test0"};
-        system::mock_addIF(info);
-        return {bus,
-                manager,
-                info,
-                "/xyz/openbmc_test/network"sv,
-                config::Parser(),
-                /*emitSignal=*/false,
-                /*nicEnabled=*/true};
-    }
-
-    void createVlan(uint16_t id)
-    {
-        system::mock_addIF(system::InterfaceInfo{
-            .idx = id + 10u, .flags = 0, .name = fmt::format("test0.{}", id)});
-        interface.createVLAN(id);
-    }
-
-    void deleteVlan(const std::string& interfaceName)
-    {
-        interface.deleteVLANObject(interfaceName);
-    }
-
-    int countIPObjects()
-    {
-        return interface.getAddresses().size();
-    }
-
-    bool isIPObjectExist(const std::string& ipaddress)
-    {
-        auto address = interface.getAddresses().find(ipaddress);
-        if (address == interface.getAddresses().end())
-        {
-            return false;
-        }
-        return true;
-    }
-
-    bool deleteIPObject(const std::string& ipaddress)
-    {
-        auto address = interface.getAddresses().find(ipaddress);
-        if (address == interface.getAddresses().end())
-        {
-            return false;
-        }
-        address->second->delete_();
-        return true;
-    }
-
-    void createIPObject(IP::Protocol addressType, const std::string& ipaddress,
-                        uint8_t subnetMask, const std::string& gateway)
-    {
-        interface.ip(addressType, ipaddress, subnetMask, gateway);
-    }
-};
-
-TEST_F(TestVlanInterface, createVLAN)
-{
-    createVlan(50);
-    fs::path filePath = confDir;
-    filePath /= "test0.50.netdev";
-
-    config::Parser parser(filePath);
-    EXPECT_EQ(parser.map, config::SectionMap(config::SectionMapInt{
-                              {"NetDev",
-                               {
-                                   {{"Name", {"test0.50"}}, {"Kind", {"vlan"}}},
-                               }},
-                              {"VLAN", {{{"Id", {"50"}}}}},
-                          }));
-}
-
-TEST_F(TestVlanInterface, deleteVLAN)
-{
-    createVlan(50);
-    deleteVlan("test0.50");
-
-    fs::path filePath = confDir;
-    filePath /= "test0.50.netdev";
-    EXPECT_FALSE(fs::is_regular_file(filePath));
-}
-
-TEST_F(TestVlanInterface, createMultipleVLAN)
-{
-    createVlan(50);
-    createVlan(60);
-
-    fs::path filePath = confDir;
-    filePath /= "test0.50.netdev";
-    config::Parser parser(filePath);
-    EXPECT_EQ(parser.map, config::SectionMap(config::SectionMapInt{
-                              {"NetDev",
-                               {
-                                   {{"Name", {"test0.50"}}, {"Kind", {"vlan"}}},
-                               }},
-                              {"VLAN", {{{"Id", {"50"}}}}},
-                          }));
-
-    filePath = confDir;
-    filePath /= "test0.60.netdev";
-    parser.setFile(filePath);
-    EXPECT_EQ(parser.map, config::SectionMap(config::SectionMapInt{
-                              {"NetDev",
-                               {
-                                   {{"Name", {"test0.60"}}, {"Kind", {"vlan"}}},
-                               }},
-                              {"VLAN", {{{"Id", {"60"}}}}},
-                          }));
-
-    deleteVlan("test0.50");
-    deleteVlan("test0.60");
-}
-
-} // namespace network
-} // namespace phosphor