Create/remove tenant bridge per tenant network at kubevirt app

Change-Id: I43ccf21db561edb9c51c2d8017dc4f614c5f0a4d
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
index c0afe45..599a615 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
@@ -51,6 +51,11 @@
     public static final int FORWARDING_TABLE = 80;
     public static final int ERROR_TABLE = 100;
 
+    // tenant integration bridge flow table index
+    public static final int TENANT_INBOUND_TABLE = 0;
+    public static final int TENANT_DHCP_TABLE = 5;
+    public static final int TENANT_FORWARDING_TABLE = 80;
+
     // flow rule priority
     public static final int PRIORITY_SWITCHING_RULE = 30000;
     public static final int PRIORITY_DHCP_RULE = 42000;
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java
index 79c535d..6c0bf8b 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java
@@ -16,8 +16,10 @@
 package org.onosproject.kubevirtnetworking.api;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import org.onlab.packet.IpAddress;
+import org.onosproject.net.DeviceId;
 
 import java.util.HashSet;
 import java.util.Objects;
@@ -25,6 +27,9 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.FLAT;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GENEVE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GRE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VXLAN;
 
 /**
  * Default implementation class of kubevirt network.
@@ -32,6 +37,8 @@
 public final class DefaultKubevirtNetwork implements KubevirtNetwork {
 
     private static final String NOT_NULL_MSG = "Network % cannot be null";
+    private static final String TENANT_BRIDGE_PREFIX = "br-int-";
+    private static final String OF_PREFIX = "of:";
 
     private final String networkId;
     private final Type type;
@@ -133,6 +140,25 @@
     }
 
     @Override
+    public String tenantBridgeName() {
+        if (type == VXLAN || type == GRE || type == GENEVE) {
+            return TENANT_BRIDGE_PREFIX + segmentIdHex(segmentId);
+        }
+        return null;
+    }
+
+    @Override
+    public DeviceId tenantDeviceId(String hostname) {
+        if (type == VXLAN || type == GRE || type == GENEVE) {
+            String dpid = genDpidFromName(tenantBridgeName() + "-" + hostname);
+            if (dpid != null) {
+                return DeviceId.deviceId(dpid);
+            }
+        }
+        return null;
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
@@ -171,6 +197,20 @@
                 .toString();
     }
 
+    private String segmentIdHex(String segIdStr) {
+        int segId = Integer.parseInt(segIdStr);
+        return String.format("%06x", segId).toLowerCase();
+    }
+
+    private String genDpidFromName(String name) {
+        if (name != null) {
+            String hexString = Integer.toHexString(name.hashCode());
+            return OF_PREFIX + Strings.padStart(hexString, 16, '0');
+        }
+
+        return null;
+    }
+
     /**
      * Returns new builder instance.
      *
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java
index 97b2286..3c99544 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java
@@ -16,6 +16,7 @@
 package org.onosproject.kubevirtnetworking.api;
 
 import org.onlab.packet.IpAddress;
+import org.onosproject.net.DeviceId;
 
 import java.util.Set;
 
@@ -121,6 +122,22 @@
     Set<IpAddress> dnses();
 
     /**
+     * Returns the tenant integration bridge name in case the bridge type
+     * is VXLAN/GRE/GENEVE.
+     *
+     * @return tunnel bridge name
+     */
+    String tenantBridgeName();
+
+    /**
+     * Returns the tenant integration bridge's device identifier.
+     *
+     * @param hostname kubevirt node hostname
+     * @return device identifier
+     */
+    DeviceId tenantDeviceId(String hostname);
+
+    /**
      * Builder of new network.
      */
     interface Builder {
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java
new file mode 100644
index 0000000..e538ef7
--- /dev/null
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.
+ */
+package org.onosproject.kubevirtnetworking.impl;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.kubevirtnetworking.api.KubevirtFlowRuleService;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetworkEvent;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetworkListener;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetworkService;
+import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
+import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
+import org.onosproject.kubevirtnode.api.KubevirtNodeListener;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.BridgeConfig;
+import org.onosproject.net.behaviour.BridgeDescription;
+import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.DefaultBridgeDescription;
+import org.onosproject.net.behaviour.DefaultPatchDescription;
+import org.onosproject.net.behaviour.InterfaceConfig;
+import org.onosproject.net.behaviour.PatchDescription;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+
+import static java.lang.Thread.sleep;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_DHCP_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_SWITCHING_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_DHCP_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_FORWARDING_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_INBOUND_TABLE;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.segmentIdHex;
+import static org.onosproject.kubevirtnode.api.Constants.TUNNEL_BRIDGE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles kubevirt network events.
+ */
+@Component(immediate = true)
+public class KubevirtNetworkHandler {
+    protected final Logger log = getLogger(getClass());
+    private static final String DEFAULT_OF_PROTO = "tcp";
+    private static final int DEFAULT_OFPORT = 6653;
+    private static final int DPID_BEGIN = 3;
+    private static final long SLEEP_MS = 3000; // we wait 3s for init each node
+
+    public static final String INTEGRATION_TO_TUNNEL_PREFIX = "i-to-t-";
+    public static final String TUNNEL_TO_INTEGRATION_PREFIX = "t-to-i-";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceAdminService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtApiConfigService apiConfigService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNetworkService networkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtFlowRuleService flowService;
+
+    private final KubevirtNetworkListener networkListener = new InternalNetworkEventListener();
+    private final KubevirtNodeListener nodeListener = new InternalNodeEventListener();
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(KUBEVIRT_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        networkService.addListener(networkListener);
+        nodeService.addListener(nodeListener);
+        leadershipService.runForLeadership(appId.name());
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        networkService.removeListener(networkListener);
+        nodeService.removeListener(nodeListener);
+        leadershipService.withdraw(appId.name());
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    private void createBridge(KubevirtNode node, KubevirtNetwork network) {
+
+        Device tunBridge = deviceService.getDevice(network.tenantDeviceId(node.hostname()));
+        if (tunBridge != null) {
+            log.warn("The tunnel bridge {} already exists at node {}",
+                    network.tenantBridgeName(), node.hostname());
+            setDefaultRules(node, network);
+            return;
+        }
+
+        Device device = deviceService.getDevice(node.ovsdb());
+
+        IpAddress serverIp = apiConfigService.apiConfig().ipAddress();
+        ControllerInfo controlInfo =
+                new ControllerInfo(serverIp, DEFAULT_OFPORT, DEFAULT_OF_PROTO);
+        List<ControllerInfo> controllers = Lists.newArrayList(controlInfo);
+
+        String dpid = network.tenantDeviceId(
+                node.hostname()).toString().substring(DPID_BEGIN);
+
+        BridgeDescription.Builder builder = DefaultBridgeDescription.builder()
+                .name(network.tenantBridgeName())
+                .failMode(BridgeDescription.FailMode.SECURE)
+                .datapathId(dpid)
+                .disableInBand()
+                .controllers(controllers);
+
+        BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
+        bridgeConfig.addBridge(builder.build());
+    }
+
+    private void removeBridge(KubevirtNode node, KubevirtNetwork network) {
+        Device device = deviceService.getDevice(node.ovsdb());
+
+        BridgeName bridgeName = BridgeName.bridgeName(network.tenantBridgeName());
+
+        BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
+        bridgeConfig.deleteBridge(bridgeName);
+        deviceService.removeDevice(network.tenantDeviceId(node.hostname()));
+    }
+
+    private void createPatchInterface(KubevirtNode node, KubevirtNetwork network) {
+        Device device = deviceService.getDevice(node.ovsdb());
+
+        if (device == null || !device.is(InterfaceConfig.class)) {
+            log.error("Failed to create patch interface on {}", node.ovsdb());
+            return;
+        }
+
+        InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
+
+        String intToTunIntf =
+                INTEGRATION_TO_TUNNEL_PREFIX + segmentIdHex(network.segmentId());
+        String tunToIntIntf =
+                TUNNEL_TO_INTEGRATION_PREFIX + segmentIdHex(network.segmentId());
+
+        // integration bridge -> tunnel bridge
+        PatchDescription brIntTunPatchDesc =
+                DefaultPatchDescription.builder()
+                        .deviceId(network.tenantBridgeName())
+                        .ifaceName(intToTunIntf)
+                        .peer(tunToIntIntf)
+                        .build();
+
+        ifaceConfig.addPatchMode(intToTunIntf, brIntTunPatchDesc);
+
+        // tunnel bridge -> integration bridge
+        PatchDescription brTunIntPatchDesc =
+                DefaultPatchDescription.builder()
+                        .deviceId(TUNNEL_BRIDGE)
+                        .ifaceName(tunToIntIntf)
+                        .peer(intToTunIntf)
+                        .build();
+        ifaceConfig.addPatchMode(tunToIntIntf, brTunIntPatchDesc);
+    }
+
+    private void removePatchInterface(KubevirtNode node, KubevirtNetwork network) {
+        Device device = deviceService.getDevice(node.ovsdb());
+
+        if (device == null || !device.is(InterfaceConfig.class)) {
+            log.error("Failed to create patch interface on {}", node.ovsdb());
+            return;
+        }
+
+        InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
+
+        String tunToIntIntf = TUNNEL_TO_INTEGRATION_PREFIX + segmentIdHex(network.segmentId());
+
+        ifaceConfig.removePatchMode(tunToIntIntf);
+    }
+
+    private void setDefaultRules(KubevirtNode node, KubevirtNetwork network) {
+        DeviceId deviceId = network.tenantDeviceId(node.hostname());
+
+        while (!deviceService.isAvailable(deviceId)) {
+            log.warn("Device {} is not ready for installing rules", deviceId);
+
+            try {
+                sleep(SLEEP_MS);
+            } catch (InterruptedException e) {
+                log.error("Failed to check device availability", e);
+            }
+        }
+
+        flowService.connectTables(deviceId, TENANT_INBOUND_TABLE, TENANT_DHCP_TABLE);
+        flowService.connectTables(deviceId, TENANT_DHCP_TABLE, TENANT_FORWARDING_TABLE);
+
+        setDhcpRule(deviceId, true);
+        setForwardingRule(deviceId, true);
+
+        log.info("Install default flow rules for tenant bridge {}", network.tenantBridgeName());
+    }
+
+    private void setDhcpRule(DeviceId deviceId, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .punt()
+                .build();
+
+        flowService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_DHCP_RULE,
+                TENANT_DHCP_TABLE,
+                install);
+    }
+
+    public void setForwardingRule(DeviceId deviceId, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder().build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.NORMAL)
+                .build();
+
+        flowService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_SWITCHING_RULE,
+                TENANT_FORWARDING_TABLE,
+                install);
+    }
+
+    private class InternalNetworkEventListener implements KubevirtNetworkListener {
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtNetworkEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_NETWORK_CREATED:
+                    eventExecutor.execute(() -> processNetworkCreation(event.subject()));
+                    break;
+                case KUBEVIRT_NETWORK_REMOVED:
+                    eventExecutor.execute(() -> processNetworkRemoval(event.subject()));
+                    break;
+                case KUBEVIRT_NETWORK_UPDATED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processNetworkCreation(KubevirtNetwork network) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            switch (network.type()) {
+                case VXLAN:
+                case GRE:
+                case GENEVE:
+                    initIntegrationTunnelBridge(network);
+                    break;
+                case FLAT:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processNetworkRemoval(KubevirtNetwork network) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            switch (network.type()) {
+                case VXLAN:
+                case GRE:
+                case GENEVE:
+                    purgeIntegrationTunnelBridge(network);
+                    break;
+                case FLAT:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void initIntegrationTunnelBridge(KubevirtNetwork network) {
+            if (network.segmentId() == null) {
+                return;
+            }
+
+            nodeService.completeNodes().forEach(n -> {
+                createBridge(n, network);
+                createPatchInterface(n, network);
+                setDefaultRules(n, network);
+            });
+        }
+
+        private void purgeIntegrationTunnelBridge(KubevirtNetwork network) {
+            if (network.segmentId() == null) {
+                return;
+            }
+
+            nodeService.completeNodes().forEach(n -> {
+                removePatchInterface(n, network);
+                removeBridge(n, network);
+            });
+        }
+    }
+
+    private class InternalNodeEventListener implements KubevirtNodeListener {
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtNodeEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_NODE_COMPLETE:
+                    eventExecutor.execute(() -> processNodeCompletion(event.subject()));
+                    break;
+                case KUBEVIRT_NODE_INCOMPLETE:
+                case KUBEVIRT_NODE_UPDATED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processNodeCompletion(KubevirtNode node) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            for (KubevirtNetwork network : networkService.networks()) {
+                switch (network.type()) {
+                    case VXLAN:
+                    case GRE:
+                    case GENEVE:
+                        if (network.segmentId() == null) {
+                            continue;
+                        }
+                        createBridge(node, network);
+                        createPatchInterface(node, network);
+                        setDefaultRules(node, network);
+                        break;
+                    case FLAT:
+                    default:
+                        // do nothing
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
index db54054..48d9bc0 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
@@ -16,6 +16,7 @@
 package org.onosproject.kubevirtnetworking.util;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
 import io.fabric8.kubernetes.api.model.Pod;
 import io.fabric8.kubernetes.client.ConfigBuilder;
 import io.fabric8.kubernetes.client.DefaultKubernetesClient;
@@ -55,6 +56,7 @@
     private static final int PORT_NAME_MAX_LENGTH = 15;
     private static final String COLON_SLASH = "://";
     private static final String COLON = ":";
+    private static final String OF_PREFIX = "of:";
 
     private static final String NETWORK_STATUS_KEY = "k8s.v1.cni.cncf.io/network-status";
     private static final String NAME = "name";
@@ -117,6 +119,21 @@
     }
 
     /**
+     * Auto generates DPID from the given name.
+     *
+     * @param name name
+     * @return auto generated DPID
+     */
+    public static String genDpidFromName(String name) {
+        if (name != null) {
+            String hexString = Integer.toHexString(name.hashCode());
+            return OF_PREFIX + Strings.padStart(hexString, 16, '0');
+        }
+
+        return null;
+    }
+
+    /**
      * Prints out the JSON string in pretty format.
      *
      * @param mapper        Object mapper
@@ -249,6 +266,17 @@
     }
 
     /**
+     * Obtains the hex string of the given segment ID with fixed padding.
+     *
+     * @param segIdStr segment identifier string
+     * @return hex string with padding
+     */
+    public static String segmentIdHex(String segIdStr) {
+        int segId = Integer.parseInt(segIdStr);
+        return String.format("%06x", segId).toLowerCase();
+    }
+
+    /**
      * Obtains the kubevirt port from kubevirt POD.
      *
      * @param networks set of existing kubevirt networks
diff --git a/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtilTest.java b/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtilTest.java
new file mode 100644
index 0000000..ef87e03
--- /dev/null
+++ b/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtilTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.
+ */
+package org.onosproject.kubevirtnetworking.util;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.segmentIdHex;
+
+/**
+ * Unit tests for kubevirt networking utils.
+ */
+public final class KubevirtNetworkingUtilTest {
+
+    /**
+     * Tests the segmentIdHex method.
+     */
+    @Test
+    public void testSegmentIdHex() {
+        assertEquals("000001", segmentIdHex("1"));
+        assertEquals("00000a", segmentIdHex("10"));
+        assertEquals("ffffff", segmentIdHex("16777215"));
+    }
+}
diff --git a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/Constants.java b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/Constants.java
index f4ffd0b..0ec60a8 100644
--- a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/Constants.java
+++ b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/Constants.java
@@ -34,6 +34,7 @@
 
     public static final String INTEGRATION_BRIDGE = "br-int";
     public static final String TUNNEL_BRIDGE = "br-tun";
+    public static final String TENANT_BRIDGE_PREFIX = "br-int-";
 
     public static final String INTEGRATION_TO_TUNNEL = "int-to-tun";
     public static final String TUNNEL_TO_INTEGRATION = "tun-to-int";
diff --git a/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DefaultKubevirtNodeHandler.java b/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DefaultKubevirtNodeHandler.java
index 55fd13c..9031666 100644
--- a/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DefaultKubevirtNodeHandler.java
+++ b/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DefaultKubevirtNodeHandler.java
@@ -81,6 +81,7 @@
 import static org.onosproject.kubevirtnode.api.Constants.INTEGRATION_TO_PHYSICAL_PREFIX;
 import static org.onosproject.kubevirtnode.api.Constants.INTEGRATION_TO_TUNNEL;
 import static org.onosproject.kubevirtnode.api.Constants.PHYSICAL_TO_INTEGRATION_SUFFIX;
+import static org.onosproject.kubevirtnode.api.Constants.TENANT_BRIDGE_PREFIX;
 import static org.onosproject.kubevirtnode.api.Constants.TUNNEL_BRIDGE;
 import static org.onosproject.kubevirtnode.api.Constants.TUNNEL_TO_INTEGRATION;
 import static org.onosproject.kubevirtnode.api.Constants.VXLAN;
@@ -620,7 +621,9 @@
             if (!phyNetworkNames.contains(brName)) {
                 // integration bridge and tunnel bridge should NOT be treated as
                 // physical bridges
-                if (brName.equals(INTEGRATION_BRIDGE) || brName.equals(TUNNEL_BRIDGE)) {
+                if (brName.equals(INTEGRATION_BRIDGE) ||
+                        brName.equals(TUNNEL_BRIDGE) ||
+                        brName.startsWith(TENANT_BRIDGE_PREFIX)) {
                     continue;
                 }
                 removePhysicalPatchPorts(node, brName.substring(NETWORK_BEGIN));