Sends GARP packet when floating ip is associated and the gateway node is changed.

Change-Id: I1bd9deb2574d97473ef9709321944116904aec8d
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java
index 96d111c..f1a5fe0 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java
@@ -48,6 +48,8 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketService;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -55,6 +57,7 @@
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.slf4j.Logger;
 
+import java.nio.ByteBuffer;
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 
@@ -70,6 +73,7 @@
 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;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.buildGarpPacket;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.externalPatchPortNum;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.gatewayNodeForSpecifiedRouter;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterMacAddress;
@@ -97,6 +101,9 @@
     protected DeviceAdminService deviceService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected KubevirtPortService kubevirtPortService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
@@ -148,9 +155,10 @@
         log.info("Stopped");
     }
 
-    private void setFloatingIpRules(KubevirtRouter router,
-                                    KubevirtFloatingIp floatingIp,
-                                    boolean install) {
+    private void setFloatingIpRulesForFip(KubevirtRouter router,
+                                          KubevirtFloatingIp floatingIp,
+                                          KubevirtNode electedGw,
+                                          boolean install) {
 
         KubevirtPort kubevirtPort = getKubevirtPort(floatingIp);
         if (kubevirtPort == null) {
@@ -164,24 +172,16 @@
             setFloatingIpDownstreamRulesToGatewayTunBridge(router, floatingIp, kubevirtNetwork, kubevirtPort, install);
         }
 
-        setFloatingIpArpResponseRules(router, floatingIp, kubevirtPort, install);
-        setFloatingIpUpstreamRules(router, floatingIp, kubevirtPort, install);
-        setFloatingIpDownstreamRules(router, floatingIp, kubevirtPort, install);
+        setFloatingIpArpResponseRules(router, floatingIp, kubevirtPort, electedGw, install);
+        setFloatingIpUpstreamRules(router, floatingIp, kubevirtPort, electedGw, install);
+        setFloatingIpDownstreamRules(router, floatingIp, kubevirtPort, electedGw, install);
     }
 
     private void setFloatingIpArpResponseRules(KubevirtRouter router,
                                                KubevirtFloatingIp floatingIp,
                                                KubevirtPort port,
+                                               KubevirtNode electedGw,
                                                boolean install) {
-
-        KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
-
-        if (electedGw == null) {
-            log.warn("Failed to install floating Ip rules for floating ip {} " +
-                    "because there's no gateway assigned to it", floatingIp.floatingIp());
-            return;
-        }
-
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchInPort(externalPatchPortNum(deviceService, electedGw))
                 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
@@ -222,16 +222,9 @@
     private void setFloatingIpUpstreamRules(KubevirtRouter router,
                                             KubevirtFloatingIp floatingIp,
                                             KubevirtPort port,
+                                            KubevirtNode electedGw,
                                             boolean install) {
 
-        KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
-
-        if (electedGw == null) {
-            log.warn("Failed to install floating Ip rules for floating ip {} " +
-                    "because there's no gateway assigned to it", floatingIp.floatingIp());
-            return;
-        }
-
         MacAddress peerMacAddress = router.peerRouter().macAddress();
 
         if (peerMacAddress == null) {
@@ -271,15 +264,8 @@
     private void setFloatingIpDownstreamRules(KubevirtRouter router,
                                               KubevirtFloatingIp floatingIp,
                                               KubevirtPort port,
+                                              KubevirtNode electedGw,
                                               boolean install) {
-        KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
-
-        if (electedGw == null) {
-            log.warn("Failed to install floating Ip rules for floating ip {} " +
-                    "because there's no gateway assigned to it", floatingIp.floatingIp());
-            return;
-        }
-
         MacAddress routerMacAddress = getRouterMacAddress(router);
 
         TrafficSelector selector = DefaultTrafficSelector.builder()
@@ -355,6 +341,31 @@
                 install);
     }
 
+    private void processGarpPacketForFloatingIp(KubevirtFloatingIp floatingIp, KubevirtNode electedGw) {
+
+        if (floatingIp == null) {
+            return;
+        }
+
+        KubevirtPort kubevirtPort = getKubevirtPort(floatingIp);
+        if (kubevirtPort == null) {
+            log.warn("Failed to install floating Ip rules for floating ip {} " +
+                    "because there's no kubevirt port associated to it", floatingIp.floatingIp());
+            return;
+        }
+
+        Ethernet ethernet = buildGarpPacket(kubevirtPort.macAddress(), floatingIp.floatingIp());
+        if (ethernet == null) {
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(externalPatchPortNum(deviceService, electedGw)).build();
+
+        packetService.emit(new DefaultOutboundPacket(electedGw.intgBridge(), treatment,
+                ByteBuffer.wrap(ethernet.serialize())));
+    }
+
     private class InternalRouterEventListener implements KubevirtRouterListener {
         private boolean isRelevantHelper() {
             return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
@@ -371,25 +382,65 @@
                     eventExecutor.execute(() -> processFloatingIpDisassociation(event.subject(),
                             event.floatingIp()));
                     break;
-
+                case KUBEVIRT_GATEWAY_NODE_CHANGED:
+                    eventExecutor.execute(() -> processRouterGatewayNodeChanged(event.subject(),
+                            event.gateway()));
+                    break;
                 default:
                     //do nothing
                     break;
             }
         }
 
+        private void processRouterGatewayNodeChanged(KubevirtRouter router, String disAssociatedGateway) {
+
+            kubevirtRouterService.floatingIps()
+                    .stream()
+                    .filter(fip -> fip.routerName().equals(router.name())).forEach(fip -> {
+                        KubevirtNode oldGw = kubevirtNodeService.node(disAssociatedGateway);
+                        if (oldGw == null) {
+                            return;
+                        }
+
+                        KubevirtNode newGw = kubevirtNodeService.node(router.electedGateway());
+                        if (newGw == null) {
+                            return;
+                        }
+
+                        setFloatingIpRulesForFip(router, fip, oldGw, false);
+
+                        setFloatingIpRulesForFip(router, fip, newGw, true);
+                        processGarpPacketForFloatingIp(fip, newGw);
+
+            });
+        }
+
         private void processFloatingIpAssociation(KubevirtRouter router, KubevirtFloatingIp floatingIp) {
-            if (!isRelevantHelper()) {
+            if (!isRelevantHelper() || router.electedGateway() == null) {
                 return;
             }
-            setFloatingIpRules(router, floatingIp, true);
+
+            KubevirtNode electedGw = kubevirtNodeService.node(router.electedGateway());
+
+            if (electedGw == null) {
+                return;
+            }
+
+            processGarpPacketForFloatingIp(floatingIp, electedGw);
+            setFloatingIpRulesForFip(router, floatingIp, electedGw, true);
         }
 
         private void processFloatingIpDisassociation(KubevirtRouter router, KubevirtFloatingIp floatingIp) {
-            if (!isRelevantHelper()) {
+            if (!isRelevantHelper() || router.electedGateway() == null) {
                 return;
             }
-            setFloatingIpRules(router, floatingIp, false);
+
+            KubevirtNode electedGw = kubevirtNodeService.node(router.electedGateway());
+
+            if (electedGw == null) {
+                return;
+            }
+            setFloatingIpRulesForFip(router, floatingIp, electedGw, false);
         }
     }
 
@@ -413,20 +464,21 @@
         }
 
         private void processNodeCompletion(KubevirtNode node) {
-            if (!isRelevantHelper()) {
+            if (!isRelevantHelper() || !node.type().equals(KubevirtNode.Type.GATEWAY)) {
                 return;
             }
 
             kubevirtRouterService.floatingIps().forEach(fip -> {
                 KubevirtRouter router = kubevirtRouterService.router(fip.routerName());
-                if (router != null) {
-                    KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+
+                if (router != null && router.electedGateway() != null) {
+                    KubevirtNode electedGw = kubevirtNodeService.node(router.electedGateway());
                     if (electedGw == null) {
                         return;
                     }
 
                     if (electedGw.hostname().equals(node.hostname())) {
-                        setFloatingIpRules(router, fip, true);
+                        setFloatingIpRulesForFip(router, fip, electedGw, true);
                     }
                 }
             });
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java
index 8323f98..a2667f9 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java
@@ -54,6 +54,8 @@
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketService;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -61,6 +63,7 @@
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.slf4j.Logger;
 
+import java.nio.ByteBuffer;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -80,6 +83,7 @@
 import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GRE;
 import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VLAN;
 import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VXLAN;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.buildGarpPacket;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.externalPatchPortNum;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.gatewayNodeForSpecifiedRouter;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterForKubevirtPort;
@@ -130,6 +134,9 @@
     protected DriverService driverService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected KubevirtRouterService kubevirtRouterService;
 
     private final ExecutorService eventExecutor = newSingleThreadExecutor(
@@ -172,15 +179,14 @@
         log.info("Stopped");
     }
 
-    private void initGatewayNodeSnatForRouter(KubevirtRouter router, boolean install) {
+    private void initGatewayNodeSnatForRouter(KubevirtRouter router, String gateway, boolean install) {
         if (router.electedGateway() == null) {
             log.warn("Fail to initialize gateway node snat for router {} " +
                     "because there's no gateway assigned to it", router.name());
             return;
         }
 
-        KubevirtNode electedGw = kubevirtNodeService.node(router.electedGateway());
-
+        KubevirtNode electedGw = kubevirtNodeService.node(gateway);
         if (electedGw == null) {
             log.warn("Fail to initialize gateway node snat for router {} " +
                     "because there's no gateway assigned to it", router.name());
@@ -454,6 +460,10 @@
                     eventExecutor.execute(() -> processRouterGatewayNodeDetached(event.subject(),
                             event.gateway()));
                     break;
+                case KUBEVIRT_GATEWAY_NODE_CHANGED:
+                    eventExecutor.execute(() -> processRouterGatewayNodeChanged(event.subject(),
+                            event.gateway()));
+                    break;
                 case KUBEVIRT_ROUTER_EXTERNAL_NETWORK_ATTACHED:
                     eventExecutor.execute(() -> processRouterExternalNetAttached(event.subject(),
                             event.externalIp(), event.externalNet(),
@@ -661,7 +671,7 @@
                 return;
             }
             if (router.enableSnat() && !router.external().isEmpty() && router.peerRouter() != null) {
-                initGatewayNodeSnatForRouter(router, true);
+                initGatewayNodeSnatForRouter(router, router.electedGateway(), true);
             }
         }
 
@@ -670,7 +680,7 @@
                 return;
             }
             if (!router.external().isEmpty() && router.peerRouter() != null) {
-                initGatewayNodeSnatForRouter(router, false);
+                initGatewayNodeSnatForRouter(router, router.electedGateway(), false);
             }
         }
 
@@ -679,9 +689,54 @@
                 return;
             }
             if (router.enableSnat() && !router.external().isEmpty() && router.peerRouter() != null) {
-                initGatewayNodeSnatForRouter(router, true);
+                initGatewayNodeSnatForRouter(router, router.electedGateway(), true);
             }
         }
+
+        private void processRouterGatewayNodeChanged(KubevirtRouter router,
+                                                     String disAssociatedGateway) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            if (router.enableSnat() && !router.external().isEmpty() && router.peerRouter() != null) {
+                initGatewayNodeSnatForRouter(router, disAssociatedGateway, false);
+                initGatewayNodeSnatForRouter(router, router.electedGateway(), true);
+
+                processRouterGatewayNodeDetached(router, disAssociatedGateway);
+                processRouterGatewayNodeAttached(router, router.electedGateway());
+
+                sendGarpPacketForSnatIp(router);
+            }
+        }
+
+        private void sendGarpPacketForSnatIp(KubevirtRouter router) {
+            if (router == null || router.electedGateway() == null) {
+                return;
+            }
+
+            String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+
+            if (routerSnatIp == null) {
+                log.warn("Fail to initialize gateway node snat for router {} " +
+                        "because there's no gateway snat ip assigned to it", router.name());
+                return;
+            }
+
+            Ethernet ethernet = buildGarpPacket(DEFAULT_GATEWAY_MAC, IpAddress.valueOf(routerSnatIp));
+
+            if (ethernet == null) {
+                return;
+            }
+
+            KubevirtNode gatewayNode = kubevirtNodeService.node(router.electedGateway());
+
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(externalPatchPortNum(deviceService, gatewayNode)).build();
+
+            packetService.emit(new DefaultOutboundPacket(gatewayNode.intgBridge(), treatment,
+                    ByteBuffer.wrap(ethernet.serialize())));
+        }
     }
 
     private class InternalKubevirtPortListener implements KubevirtPortListener {
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 a6ac229..14bc79c 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
@@ -28,6 +28,8 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
@@ -561,6 +563,13 @@
         return port != null ? port.number() : null;
     }
 
+    /**
+     * Returns the kubevirt external network with specified router.
+     *
+     * @param networkService kubevirt network service
+     * @param router kubevirt router
+     * @return external network
+     */
     public static KubevirtNetwork getExternalNetworkByRouter(KubevirtNetworkService networkService,
                                                              KubevirtRouter router) {
         String networkId = router.external().values().stream().findAny().orElse(null);
@@ -570,4 +579,33 @@
 
         return networkService.network(networkId);
     }
+
+    public static Ethernet buildGarpPacket(MacAddress srcMac, IpAddress srcIp) {
+        if (srcMac == null || srcIp == null) {
+            return null;
+        }
+
+        Ethernet ethernet = new Ethernet();
+        ethernet.setDestinationMACAddress(MacAddress.BROADCAST);
+        ethernet.setSourceMACAddress(srcMac);
+        ethernet.setEtherType(Ethernet.TYPE_ARP);
+
+        ARP arp = new ARP();
+        arp.setOpCode(ARP.OP_REPLY);
+        arp.setProtocolType(ARP.PROTO_TYPE_IP);
+        arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
+
+        arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
+        arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
+
+        arp.setSenderHardwareAddress(srcMac.toBytes());
+        arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
+
+        arp.setSenderProtocolAddress(srcIp.toOctets());
+        arp.setTargetProtocolAddress(srcIp.toOctets());
+
+        ethernet.setPayload(arp);
+
+        return ethernet;
+    }
 }