Fix: remove FloatingIp related flow rules when removing a VM

Change-Id: I0de430421ccece5a278d7629ec4d54ec41b6d02a
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
index 0819e40..bd38cd3 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
@@ -16,6 +16,7 @@
 package org.onosproject.openstacknetworking.impl;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -30,12 +31,16 @@
 import org.onosproject.cluster.NodeId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.net.Host;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
 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.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
 import org.onosproject.openstacknetworking.api.Constants;
 import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
 import org.onosproject.openstacknetworking.api.InstancePort;
@@ -60,7 +65,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
@@ -71,6 +78,8 @@
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_EXTERNAL;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_INTERNAL;
 import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
 import static org.onosproject.openstacknetworking.util.RulePopulatorUtil.buildExtension;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 
@@ -98,6 +107,9 @@
     protected ClusterService clusterService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OpenstackNodeService osNodeService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -116,6 +128,8 @@
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
     private final OpenstackRouterListener floatingIpLisener = new InternalFloatingIpListener();
     private final OpenstackNodeListener osNodeListener = new InternalNodeListener();
+    private final HostListener hostListener = new InternalHostListener();
+    private Map<MacAddress, InstancePort> removedPorts = Maps.newConcurrentMap();
 
     private ApplicationId appId;
     private NodeId localNodeId;
@@ -125,6 +139,7 @@
         appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
         localNodeId = clusterService.getLocalNode().id();
         leadershipService.runForLeadership(appId.name());
+        hostService.addListener(hostListener);
         osRouterService.addListener(floatingIpLisener);
         osNodeService.addListener(osNodeListener);
 
@@ -133,6 +148,7 @@
 
     @Deactivate
     protected void deactivate() {
+        hostService.removeListener(hostListener);
         osNodeService.removeListener(osNodeListener);
         osRouterService.removeListener(floatingIpLisener);
         leadershipService.withdraw(appId.name());
@@ -155,6 +171,13 @@
         MacAddress srcMac = MacAddress.valueOf(osPort.getMacAddress());
         log.trace("Mac address of openstack port: {}", srcMac);
         InstancePort instPort = instancePortService.instancePort(srcMac);
+
+        // sweep through removed port map
+        if (instPort == null) {
+            instPort = removedPorts.get(srcMac);
+            removedPorts.remove(srcMac);
+        }
+
         if (instPort == null) {
             final String errorFormat = ERR_FLOW + "no host(MAC:%s) found";
             final String error = String.format(errorFormat,
@@ -421,6 +444,34 @@
         return osNetworkService.externalPeerRouter(exGatewayInfo);
     }
 
+
+    private void associateFloatingIp(NetFloatingIP osFip) {
+        Port osPort = osNetworkService.port(osFip.getPortId());
+        if (osPort == null) {
+            final String errorFormat = ERR_FLOW + "port(%s) not found";
+            final String error = String.format(errorFormat,
+                    osFip.getFloatingIpAddress(), osFip.getPortId());
+            throw new IllegalStateException(error);
+        }
+        // set floating IP rules only if the port is associated to a VM
+        if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
+            setFloatingIpRules(osFip, osPort, true);
+        }
+    }
+
+    private void disassociateFloatingIp(NetFloatingIP osFip, String portId) {
+        Port osPort = osNetworkService.port(portId);
+        if (osPort == null) {
+            // FIXME when a port with floating IP removed without
+            // disassociation step, it can reach here
+            return;
+        }
+        // set floating IP rules only if the port is associated to a VM
+        if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
+            setFloatingIpRules(osFip, osPort, false);
+        }
+    }
+
     private class InternalFloatingIpListener implements OpenstackRouterListener {
 
         @Override
@@ -482,33 +533,6 @@
                     break;
             }
         }
-
-        private void associateFloatingIp(NetFloatingIP osFip) {
-            Port osPort = osNetworkService.port(osFip.getPortId());
-            if (osPort == null) {
-                final String errorFormat = ERR_FLOW + "port(%s) not found";
-                final String error = String.format(errorFormat,
-                        osFip.getFloatingIpAddress(), osFip.getPortId());
-                throw new IllegalStateException(error);
-            }
-            // set floating IP rules only if the port is associated to a VM
-            if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
-                setFloatingIpRules(osFip, osPort, true);
-            }
-        }
-
-        private void disassociateFloatingIp(NetFloatingIP osFip, String portId) {
-            Port osPort = osNetworkService.port(portId);
-            if (osPort == null) {
-                // FIXME when a port with floating IP removed without
-                // disassociation step, it can reach here
-                return;
-            }
-            // set floating IP rules only if the port is associated to a VM
-            if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
-                setFloatingIpRules(osFip, osPort, false);
-            }
-        }
     }
 
     private class InternalNodeListener implements OpenstackNodeListener {
@@ -552,4 +576,57 @@
             }
         }
     }
+
+    private class InternalHostListener implements HostListener {
+
+        @Override
+        public boolean isRelevant(HostEvent event) {
+            Host host = event.subject();
+            if (!isValidHost(host)) {
+                log.debug("Invalid host detected, ignore it {}", host);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void event(HostEvent event) {
+            InstancePort instPort = HostBasedInstancePort.of(event.subject());
+            switch (event.type()) {
+                case HOST_REMOVED:
+                    storeTempInstPort(instPort);
+                    break;
+                case HOST_UPDATED:
+                case HOST_ADDED:
+                default:
+                    break;
+            }
+        }
+
+        private void storeTempInstPort(InstancePort port) {
+            Set<NetFloatingIP> ips = osRouterService.floatingIps();
+            for (NetFloatingIP fip : ips) {
+                if (Strings.isNullOrEmpty(fip.getFixedIpAddress())) {
+                    continue;
+                }
+                if (Strings.isNullOrEmpty(fip.getFloatingIpAddress())) {
+                    continue;
+                }
+                if (fip.getFixedIpAddress().equals(port.ipAddress().toString())) {
+                    removedPorts.put(port.macAddress(), port);
+                    eventExecutor.execute(() -> {
+                        disassociateFloatingIp(fip, port.portId());
+                        log.info("Disassociated floating IP {}:{}",
+                                fip.getFloatingIpAddress(), fip.getFixedIpAddress());
+                    });
+                }
+            }
+        }
+
+        private boolean isValidHost(Host host) {
+            return !host.ipAddresses().isEmpty() &&
+                    host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
+                    host.annotations().value(ANNOTATION_PORT_ID) != null;
+        }
+    }
 }