Remove bridges and associated flow rules when removing k8s nodes

Change-Id: Iab54503a4bb75874f69e8e1623bb11c66cad9eee
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sHostHandler.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sHostHandler.java
index 2fc26be..a3a6fcd 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sHostHandler.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sHostHandler.java
@@ -448,6 +448,7 @@
             log.error("Exception caused during init state checking...");
         }
 
+        // checks whether all tunneling ports exist
         for (K8sTunnelBridge bridge: k8sHost.tunBridges()) {
             if (!isTunPortEnabled(bridge, bridge.vxlanPortName())) {
                 return false;
@@ -460,6 +461,26 @@
             }
         }
 
+        // checks whether all patch ports attached to tunnel bridge exist
+        for (K8sTunnelBridge bridge : k8sHost.tunBridges()) {
+            for (String node : k8sHost.nodeNames()) {
+                K8sNode k8sNode = k8sNodeAdminService.node(node);
+                if (!isTunPortEnabled(bridge, k8sNode.tunToIntgPatchPortName())) {
+                    return false;
+                }
+            }
+        }
+
+        // checks whether all patch ports attached to router bridge exist
+        for (K8sRouterBridge bridge : k8sHost.routerBridges()) {
+            for (String node : k8sHost.nodeNames()) {
+                K8sNode k8sNode = k8sNodeAdminService.node(node);
+                if (!isRouterPortEnabled(bridge, k8sNode.routerToExtPatchPortName())) {
+                    return false;
+                }
+            }
+        }
+
         return true;
     }
 
@@ -471,6 +492,14 @@
                                 port.isEnabled());
     }
 
+    private boolean isRouterPortEnabled(K8sRouterBridge routerBridge, String intf) {
+        return deviceService.isAvailable(routerBridge.deviceId()) &&
+                deviceService.getPorts(routerBridge.deviceId()).stream()
+                        .anyMatch(port -> Objects.equals(
+                                port.annotations().value(PORT_NAME), intf) &&
+                                port.isEnabled());
+    }
+
     /**
      * Configures the kubernetes host with new state.
      *
@@ -600,29 +629,30 @@
                             return;
                         }
 
-                        K8sHost k8sHost = k8sHostAdminService.hostByTunBridge(device.id());
+                        K8sHost tunnelHost = k8sHostAdminService.hostByTunBridge(device.id());
 
-                        if (k8sHost == null) {
+                        if (tunnelHost == null) {
                             return;
                         }
 
-                        Port port = event.port();
-                        String portName = port.annotations().value(PORT_NAME);
-                        if (k8sHost.state() == DEVICE_CREATED) {
-
-                            K8sTunnelBridge tunBridge = k8sHost.tunBridges().stream().filter(
+                        if (tunnelHost.state() == DEVICE_CREATED) {
+                            // we bootstrap the host whenever any ports added to the tunnel bridge
+                            tunnelHost.tunBridges().stream().filter(
                                     br -> br.deviceId().equals(device.id())
-                            ).findAny().orElse(null);
+                            ).findAny().ifPresent(tunBridge -> bootstrapHost(tunnelHost));
+                        }
 
-                            if (tunBridge != null) {
-                                if (Objects.equals(portName, tunBridge.vxlanPortName()) ||
-                                        Objects.equals(portName, tunBridge.grePortName()) ||
-                                        Objects.equals(portName, tunBridge.genevePortName())) {
-                                    log.info("Interface {} added or updated to {}",
-                                            portName, device.id());
-                                    bootstrapHost(k8sHost);
-                                }
-                            }
+                        K8sHost routerHost = k8sHostAdminService.hostByRouterBridge(device.id());
+
+                        if (routerHost == null) {
+                            return;
+                        }
+
+                        if (routerHost.state() == DEVICE_CREATED) {
+                            // we bootstrap the host whenever any ports added to the router bridge
+                            routerHost.routerBridges().stream().filter(
+                                    br -> br.deviceId().equals(device.id())
+                            ).findAny().ifPresent(routerBridge -> bootstrapHost(routerHost));
                         }
                     });
                     break;
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java
index 46f56a6..e5c8b00 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java
@@ -86,6 +86,7 @@
 import static org.onosproject.net.AnnotationKeys.PORT_NAME;
 import static org.slf4j.LoggerFactory.getLogger;
 
+
 /**
  * Service bootstraps kubernetes node based on its type.
  */
@@ -261,6 +262,11 @@
         // do something if needed
     }
 
+    @Override
+    public void processOffBoardedState(K8sNode k8sNode) {
+        // do something if needed
+    }
+
     /**
      * Extracts properties from the component configuration context.
      *
@@ -630,6 +636,18 @@
             return;
         }
 
+        if (k8sNode.mode() == NORMAL) {
+            // delete tunnel bridge from the node
+            client.dropBridge(k8sNode.tunBridgeName());
+        } else {
+            // remove the patch ports direct to the integration bridge from tunnel bridge
+            removeTunnelPatchPort(k8sNode);
+            // remove the patch ports direct to the external bridge from the router bridge
+            removeRouterPatchPort(k8sNode);
+            // remove the patch ports directs to the openstack's br-int bridge from the int and ext bridges
+            removeOpenstackPatchPorts(k8sNode);
+        }
+
         // delete integration bridge from the node
         client.dropBridge(k8sNode.intgBridgeName());
 
@@ -639,13 +657,42 @@
         // delete local bridge from the node
         client.dropBridge(k8sNode.localBridgeName());
 
-        if (k8sNode.mode() == NORMAL) {
-            // delete tunnel bridge from the node
-            client.dropBridge(k8sNode.tunBridgeName());
+        // disconnect ovsdb
+        // client.disconnect();
+    }
+
+    private void removeTunnelPatchPort(K8sNode k8sNode) {
+        OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
+        if (client == null) {
+            log.info("Failed to get ovsdb client");
+            return;
         }
 
-        // disconnect ovsdb
-        client.disconnect();
+        client.dropInterface(k8sNode.tunToIntgPatchPortName());
+    }
+
+    private void removeRouterPatchPort(K8sNode k8sNode) {
+        OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
+        if (client == null) {
+            log.info("Failed to get ovsdb client");
+            return;
+        }
+
+        client.dropInterface(k8sNode.routerToExtPatchPortName());
+    }
+
+    private void removeOpenstackPatchPorts(K8sNode k8sNode) {
+        OvsdbClientService client = getOvsdbClient(k8sNode, ovsdbPortNum, ovsdbController);
+        if (client == null) {
+            log.info("Failed to get ovsdb client");
+            return;
+        }
+
+        // remove patch port attached at br-int peers with the k8s integration bridge
+        client.dropInterface(k8sNode.osToK8sIntgPatchPortName());
+
+        // remove patch port attached at br-int peers with the k8s external bridge
+        client.dropInterface(k8sNode.osToK8sExtPatchPortName());
     }
 
     /**
@@ -831,21 +878,17 @@
                 case K8S_NODE_CREATED:
                 case K8S_NODE_UPDATED:
                     eventExecutor.execute(() -> {
-
                         if (!isRelevantHelper()) {
                             return;
                         }
-
                         bootstrapNode(event.subject());
                     });
                     break;
                 case K8S_NODE_REMOVED:
                     eventExecutor.execute(() -> {
-
                         if (!isRelevantHelper()) {
                             return;
                         }
-
                         processK8sNodeRemoved(event.subject());
                     });
                     break;
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java
index 90ec2a0..b221ac4 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java
@@ -51,13 +51,8 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.k8snode.api.K8sNodeEvent.Type.K8S_NODE_COMPLETE;
-import static org.onosproject.k8snode.api.K8sNodeEvent.Type.K8S_NODE_CREATED;
-import static org.onosproject.k8snode.api.K8sNodeEvent.Type.K8S_NODE_INCOMPLETE;
-import static org.onosproject.k8snode.api.K8sNodeEvent.Type.K8S_NODE_REMOVED;
-import static org.onosproject.k8snode.api.K8sNodeEvent.Type.K8S_NODE_UPDATED;
-import static org.onosproject.k8snode.api.K8sNodeState.COMPLETE;
-import static org.onosproject.k8snode.api.K8sNodeState.INCOMPLETE;
+import static org.onosproject.k8snode.api.K8sNodeEvent.Type.*;
+import static org.onosproject.k8snode.api.K8sNodeState.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -190,6 +185,11 @@
                                     K8S_NODE_INCOMPLETE,
                                     event.newValue().value()
                             ));
+                        } else if (event.newValue().value().state() == OFF_BOARDED) {
+                            notifyDelegate(new K8sNodeEvent(
+                                    K8S_NODE_OFF_BOARDED,
+                                    event.newValue().value()
+                            ));
                         }
                     });
                     break;
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java
index 1747ada..b527dd1 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java
@@ -206,6 +206,18 @@
         return null;
     }
 
+    @Override
+    public K8sHost hostByRouterBridge(DeviceId deviceId) {
+        for (K8sHost host : hostStore.hosts()) {
+            long cnt = host.routerBridges().stream().filter(
+                    br -> br.dpid().equals(deviceId.toString())).count();
+            if (cnt > 0) {
+                return host;
+            }
+        }
+        return null;
+    }
+
     private class InternalHostStoreDelegate implements K8sHostStoreDelegate {
 
         @Override