[ONOS-7684] Support VM Live Migration (VxLAN + VLAN)

Change-Id: I4717f0af6731b41eaf3114994f2087af74c3e3f5
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
index 4cadb17..8c3c56d 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
@@ -57,6 +57,8 @@
 import org.onosproject.net.packet.PacketService;
 import org.onosproject.openstacknetworking.api.Constants;
 import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
 import org.onosproject.openstacknetworking.api.InstancePortService;
 import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
@@ -96,7 +98,10 @@
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
 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.OpenstackNetworkingUtil.associatedFloatingIp;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getGwByComputeDevId;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getGwByInstancePort;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.isAssociatedWithVM;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -159,11 +164,13 @@
     private final OpenstackRouterListener osRouterListener = new InternalRouterEventListener();
     private final HostListener hostListener = new InternalHostListener();
     private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
+    private final InstancePortListener instPortListener = new InternalInstancePortListener();
 
     private ApplicationId appId;
     private NodeId localNodeId;
     private Map<String, MacAddress> floatingIpMacMap = Maps.newConcurrentMap();
     private Map<MacAddress, InstancePort> removedPorts = Maps.newConcurrentMap();
+    private Map<String, DeviceId> migrationPool = Maps.newConcurrentMap();
 
     private final ExecutorService eventExecutor = newSingleThreadExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
@@ -178,6 +185,7 @@
         hostService.addListener(hostListener);
         osRouterService.addListener(osRouterListener);
         osNodeService.addListener(osNodeListener);
+        instancePortService.addListener(instPortListener);
         leadershipService.runForLeadership(appId.name());
         packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
         log.info("Started");
@@ -189,6 +197,7 @@
         hostService.removeListener(hostListener);
         osRouterService.removeListener(osRouterListener);
         osNodeService.removeListener(osNodeListener);
+        instancePortService.removeListener(instPortListener);
         leadershipService.withdraw(appId.name());
         eventExecutor.shutdown();
         configService.unregisterProperties(getClass(), false);
@@ -410,6 +419,33 @@
     }
 
     /**
+     * Installs/uninstalls ARP flow rules to the corresponding gateway by
+     * looking for compute node's device ID.
+     *
+     * @param fip       floating IP
+     * @param port      instance port
+     * @param gateways  a collection of gateways
+     * @param install   install flag
+     */
+    private void setFloatingIpArpRuleWithPortEvent(NetFloatingIP fip,
+                                                   InstancePort port,
+                                                   Set<OpenstackNode> gateways,
+                                                   boolean install) {
+        if (arpMode.equals(ARP_BROADCAST_MODE)) {
+
+            OpenstackNode gw = getGwByInstancePort(gateways, port);
+
+            if (gw == null) {
+                return;
+            }
+
+            String macString = osNetworkAdminService.port(fip.getPortId()).getMacAddress();
+
+            setArpRule(fip, MacAddress.valueOf(macString), gw, install);
+        }
+    }
+
+    /**
      * Installs static ARP rules used in ARP BROAD_CAST mode.
      * Note that, those rules will be only matched ARP_REQUEST packets,
      * used for telling gateway node the mapped MAC address of requested IP,
@@ -460,36 +496,41 @@
                 return;
             }
 
-            TrafficSelector selector = DefaultTrafficSelector.builder()
-                    .matchEthType(EthType.EtherType.ARP.ethType().toShort())
-                    .matchArpOp(ARP.OP_REQUEST)
-                    .matchArpTpa(Ip4Address.valueOf(fip.getFloatingIpAddress()))
-                    .build();
+            setArpRule(fip, targetMac, gw, install);
+        }
+    }
 
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                    .setArpOp(ARP.OP_REPLY)
-                    .setArpSha(targetMac)
-                    .setArpSpa(Ip4Address.valueOf(fip.getFloatingIpAddress()))
-                    .setOutput(PortNumber.IN_PORT)
-                    .build();
+    private void setArpRule(NetFloatingIP fip, MacAddress targetMac,
+                            OpenstackNode gateway, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .matchArpOp(ARP.OP_REQUEST)
+                .matchArpTpa(Ip4Address.valueOf(fip.getFloatingIpAddress()))
+                .build();
 
-            osFlowRuleService.setRule(
-                    appId,
-                    gw.intgBridge(),
-                    selector,
-                    treatment,
-                    PRIORITY_ARP_GATEWAY_RULE,
-                    GW_COMMON_TABLE,
-                    install
-            );
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setArpOp(ARP.OP_REPLY)
+                .setArpSha(targetMac)
+                .setArpSpa(Ip4Address.valueOf(fip.getFloatingIpAddress()))
+                .setOutput(PortNumber.IN_PORT)
+                .build();
 
-            if (install) {
-                log.info("Install ARP Rule for Floating IP {}",
-                        fip.getFloatingIpAddress());
-            } else {
-                log.info("Uninstall ARP Rule for Floating IP {}",
-                        fip.getFloatingIpAddress());
-            }
+        osFlowRuleService.setRule(
+                appId,
+                gateway.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_ARP_GATEWAY_RULE,
+                GW_COMMON_TABLE,
+                install
+        );
+
+        if (install) {
+            log.info("Install ARP Rule for Floating IP {}",
+                    fip.getFloatingIpAddress());
+        } else {
+            log.info("Uninstall ARP Rule for Floating IP {}",
+                    fip.getFloatingIpAddress());
         }
     }
 
@@ -780,4 +821,58 @@
             );
         }
     }
+
+    private class InternalInstancePortListener implements InstancePortListener {
+
+        @Override
+        public boolean isRelevant(InstancePortEvent event) {
+            Set<NetFloatingIP> ips = osRouterService.floatingIps();
+            NetFloatingIP fip = associatedFloatingIp(event.subject(), ips);
+            Set<OpenstackNode> gateways = osNodeService.completeNodes(GATEWAY);
+
+            if (gateways.size() == 1) {
+                return false;
+            }
+
+            return fip != null && isAssociatedWithVM(osNetworkService, fip);
+        }
+
+        @Override
+        public void event(InstancePortEvent event) {
+            Set<NetFloatingIP> ips = osRouterService.floatingIps();
+            NetFloatingIP fip = associatedFloatingIp(event.subject(), ips);
+            Set<OpenstackNode> gateways = osNodeService.completeNodes(GATEWAY);
+
+            switch (event.type()) {
+                case OPENSTACK_INSTANCE_MIGRATION_STARTED:
+
+                    migrationPool.put(fip.getFloatingIpAddress(), event.subject().deviceId());
+
+                    eventExecutor.execute(() -> {
+                        setFloatingIpArpRuleWithPortEvent(fip, event.subject(),
+                                gateways, true);
+                    });
+                    break;
+                case OPENSTACK_INSTANCE_MIGRATION_ENDED:
+
+                    DeviceId newDeviceId = migrationPool.get(fip.getFloatingIpAddress());
+                    DeviceId oldDeviceId = event.subject().deviceId();
+                    migrationPool.remove(fip.getFloatingIpAddress());
+
+                    OpenstackNode oldGw = getGwByComputeDevId(gateways, oldDeviceId);
+                    OpenstackNode newGw = getGwByComputeDevId(gateways, newDeviceId);
+
+                    if (oldGw != null && oldGw.equals(newGw)) {
+                        return;
+                    }
+
+                    eventExecutor.execute(() ->
+                        setFloatingIpArpRuleWithPortEvent(fip, event.subject(),
+                                gateways, false));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
 }