Fix: avoid ICMP DUP replies caused by duplicated rules in GW nodes

Change-Id: Idabdbd5a7643a5d4fe28f552ef427d9f064cecc2
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 dfe20b8..ee8688d 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
@@ -195,8 +195,14 @@
             throw new IllegalStateException(errorFormat);
         }
 
-        setComputeNodeToGateway(instPort, osNet, gateway, install);
-        setDownstreamRules(floatingIp, osNet, instPort, externalPeerRouter, install);
+        updateComputeNodeRules(instPort, osNet, gateway, install);
+        updateGatewayNodeRules(floatingIp, instPort, osNet, externalPeerRouter, gateway, install);
+
+        // FIXME: downstream internal rules are still duplicated in all gateway nodes
+        // need to make the internal rules de-duplicated sooner or later
+        setDownstreamInternalRules(floatingIp, osNet, instPort, install);
+
+        // TODO: need to refactor setUpstreamRules if possible
         setUpstreamRules(floatingIp, osNet, instPort, externalPeerRouter, install);
         log.trace("Succeeded to set flow rules for floating ip {}:{} and install: {}",
                 floatingIp.getFloatingIpAddress(),
@@ -204,10 +210,52 @@
                 install);
     }
 
-    private synchronized void setComputeNodeToGateway(InstancePort instPort,
-                                                      Network osNet,
-                                                      OpenstackNode gateway,
-                                                      boolean install) {
+    private synchronized void updateGatewayNodeRules(NetFloatingIP fip,
+                                                     InstancePort instPort,
+                                                     Network osNet,
+                                                     ExternalPeerRouter router,
+                                                     OpenstackNode gateway,
+                                                     boolean install) {
+
+        Set<OpenstackNode> completedGws = osNodeService.completeNodes(GATEWAY);
+        Set<OpenstackNode> finalGws = Sets.newConcurrentHashSet();
+        finalGws.addAll(ImmutableSet.copyOf(completedGws));
+
+        if (install) {
+            if (completedGws.contains(gateway)) {
+                if (completedGws.size() > 1) {
+                    finalGws.remove(gateway);
+                    if (fip.getPortId() != null) {
+                        setDownstreamExternalRulesHelper(fip, osNet, instPort, router,
+                                ImmutableSet.copyOf(finalGws), false);
+                        finalGws.add(gateway);
+                    }
+                }
+                if (fip.getPortId() != null) {
+                    setDownstreamExternalRulesHelper(fip, osNet, instPort, router,
+                            ImmutableSet.copyOf(finalGws), true);
+                }
+            } else {
+                log.warn("Detected node should be included in completed gateway set");
+            }
+        } else {
+            if (!completedGws.contains(gateway)) {
+                if (completedGws.size() >= 1) {
+                    if (fip.getPortId() != null) {
+                        setDownstreamExternalRulesHelper(fip, osNet, instPort, router,
+                                ImmutableSet.copyOf(finalGws), true);
+                    }
+                }
+            } else {
+                log.warn("Detected node should NOT be included in completed gateway set");
+            }
+        }
+    }
+
+    private synchronized void updateComputeNodeRules(InstancePort instPort,
+                                                     Network osNet,
+                                                     OpenstackNode gateway,
+                                                     boolean install) {
 
         Set<OpenstackNode> completedGws = osNodeService.completeNodes(GATEWAY);
         Set<OpenstackNode> finalGws = Sets.newConcurrentHashSet();
@@ -217,6 +265,7 @@
             // these are floating IP related cases...
             setComputeNodeToGatewayHelper(instPort, osNet,
                     ImmutableSet.copyOf(finalGws), install);
+
         } else {
             // these are openstack node related cases...
             if (install) {
@@ -302,9 +351,10 @@
         log.trace("Succeeded to set flow rules from compute node to gateway on compute node");
     }
 
-    private void setDownstreamRules(NetFloatingIP floatingIp, Network osNet,
-                                    InstancePort instPort, ExternalPeerRouter externalPeerRouter,
-                                    boolean install) {
+    private void setDownstreamInternalRules(NetFloatingIP floatingIp,
+                                            Network osNet,
+                                            InstancePort instPort,
+                                            boolean install) {
         OpenstackNode cNode = osNodeService.node(instPort.deviceId());
         if (cNode == null) {
             final String error = String.format("Cannot find openstack node for device %s",
@@ -324,51 +374,8 @@
 
         IpAddress floating = IpAddress.valueOf(floatingIp.getFloatingIpAddress());
 
+        // TODO: following code snippet will be refactored sooner or later
         osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
-            TrafficSelector.Builder externalSelectorBuilder = DefaultTrafficSelector.builder()
-                    .matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPDst(floating.toIpPrefix());
-
-            TrafficTreatment.Builder externalTreatmentBuilder = DefaultTrafficTreatment.builder()
-                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
-                    .setEthDst(instPort.macAddress())
-                    .setIpDst(instPort.ipAddress().getIp4Address());
-
-            if (!externalPeerRouter.externalPeerRouterVlanId().equals(VlanId.NONE)) {
-                externalSelectorBuilder.matchVlanId(externalPeerRouter.externalPeerRouterVlanId()).build();
-                externalTreatmentBuilder.popVlan();
-            }
-
-            switch (osNet.getNetworkType()) {
-                case VXLAN:
-                    externalTreatmentBuilder.setTunnelId(Long.valueOf(osNet.getProviderSegID()))
-                            .extension(buildExtension(
-                                    deviceService,
-                                    gNode.intgBridge(),
-                                    cNode.dataIp().getIp4Address()),
-                                    gNode.intgBridge())
-                            .setOutput(gNode.tunnelPortNum());
-                    break;
-                case VLAN:
-                    externalTreatmentBuilder.pushVlan()
-                            .setVlanId(VlanId.vlanId(osNet.getProviderSegID()))
-                            .setOutput(gNode.vlanPortNum());
-                    break;
-                default:
-                    final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
-                            osNet.getNetworkType());
-                    throw new IllegalStateException(error);
-            }
-
-            osFlowRuleService.setRule(
-                    appId,
-                    gNode.intgBridge(),
-                    externalSelectorBuilder.build(),
-                    externalTreatmentBuilder.build(),
-                    PRIORITY_FLOATING_EXTERNAL,
-                    GW_COMMON_TABLE,
-                    install);
-
             // access from one VM to the others via floating IP
             TrafficSelector internalSelector = DefaultTrafficSelector.builder()
                     .matchEthType(Ethernet.TYPE_IPV4)
@@ -414,6 +421,82 @@
         log.trace("Succeeded to set flow rules for downstream on gateway nodes");
     }
 
+    private void setDownstreamExternalRulesHelper(NetFloatingIP floatingIp,
+                                                  Network osNet,
+                                                  InstancePort instPort,
+                                                  ExternalPeerRouter externalPeerRouter,
+                                                  Set<OpenstackNode> gateways, boolean install) {
+        OpenstackNode cNode = osNodeService.node(instPort.deviceId());
+        if (cNode == null) {
+            final String error = String.format("Cannot find openstack node for device %s",
+                    instPort.deviceId());
+            throw new IllegalStateException(error);
+        }
+        if (osNet.getNetworkType() == NetworkType.VXLAN && cNode.dataIp() == null) {
+            final String errorFormat = ERR_FLOW + "VXLAN mode is not ready for %s";
+            final String error = String.format(errorFormat, floatingIp, cNode.hostname());
+            throw new IllegalStateException(error);
+        }
+        if (osNet.getNetworkType() == NetworkType.VLAN && cNode.vlanIntf() == null) {
+            final String errorFormat = ERR_FLOW + "VLAN mode is not ready for %s";
+            final String error = String.format(errorFormat, floatingIp, cNode.hostname());
+            throw new IllegalStateException(error);
+        }
+
+        IpAddress floating = IpAddress.valueOf(floatingIp.getFloatingIpAddress());
+
+        OpenstackNode selectedGatewayNode = getGwByComputeDevId(gateways, instPort.deviceId());
+
+        if (selectedGatewayNode == null) {
+            final String errorFormat = ERR_FLOW + "no gateway node selected";
+            throw new IllegalStateException(errorFormat);
+        }
+
+        TrafficSelector.Builder externalSelectorBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(floating.toIpPrefix());
+
+        TrafficTreatment.Builder externalTreatmentBuilder = DefaultTrafficTreatment.builder()
+                .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                .setEthDst(instPort.macAddress())
+                .setIpDst(instPort.ipAddress().getIp4Address());
+
+        if (!externalPeerRouter.externalPeerRouterVlanId().equals(VlanId.NONE)) {
+            externalSelectorBuilder.matchVlanId(externalPeerRouter.externalPeerRouterVlanId()).build();
+            externalTreatmentBuilder.popVlan();
+        }
+
+        switch (osNet.getNetworkType()) {
+            case VXLAN:
+                externalTreatmentBuilder.setTunnelId(Long.valueOf(osNet.getProviderSegID()))
+                        .extension(buildExtension(
+                                deviceService,
+                                selectedGatewayNode.intgBridge(),
+                                cNode.dataIp().getIp4Address()),
+                                selectedGatewayNode.intgBridge())
+                        .setOutput(selectedGatewayNode.tunnelPortNum());
+                break;
+            case VLAN:
+                externalTreatmentBuilder.pushVlan()
+                        .setVlanId(VlanId.vlanId(osNet.getProviderSegID()))
+                        .setOutput(selectedGatewayNode.vlanPortNum());
+                break;
+            default:
+                final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+                        osNet.getNetworkType());
+                throw new IllegalStateException(error);
+        }
+
+        osFlowRuleService.setRule(
+                appId,
+                selectedGatewayNode.intgBridge(),
+                externalSelectorBuilder.build(),
+                externalTreatmentBuilder.build(),
+                PRIORITY_FLOATING_EXTERNAL,
+                GW_COMMON_TABLE,
+                install);
+    }
+
     private void setUpstreamRules(NetFloatingIP floatingIp, Network osNet,
                                   InstancePort instPort, ExternalPeerRouter externalPeerRouter,
                                   boolean install) {
@@ -651,7 +734,15 @@
                                 throw new IllegalStateException(error);
                             }
 
-                            setComputeNodeToGateway(instPort, osNet, event.subject(), false);
+                            ExternalPeerRouter externalPeerRouter = externalPeerRouter(osNet);
+                            if (externalPeerRouter == null) {
+                                final String errorFormat = ERR_FLOW + "no external peer router found";
+                                throw new IllegalStateException(errorFormat);
+                            }
+
+                            updateComputeNodeRules(instPort, osNet, event.subject(), false);
+                            updateGatewayNodeRules(fip, instPort, osNet,
+                                    externalPeerRouter, event.subject(), false);
                         }
                     });
                     break;