[ONOS-5176] Refactoring: improves SONA pipeline and OpenstackRoutingManager

Change-Id: I6e582fff781c2e69fb6ef4b34d8e41767202fc20
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java
index c8ce876..f7989bd 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.openstacknetworking.routing;
 
-import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -25,10 +24,11 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
 import org.onlab.util.Tools;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
 import org.onosproject.core.GroupId;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
@@ -38,12 +38,10 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flowobjective.DefaultForwardingObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
 import org.onosproject.openstackinterface.OpenstackInterfaceService;
 import org.onosproject.openstackinterface.OpenstackNetwork;
-import org.onosproject.openstackinterface.OpenstackPort;
 import org.onosproject.openstackinterface.OpenstackRouter;
 import org.onosproject.openstackinterface.OpenstackRouterInterface;
 import org.onosproject.openstackinterface.OpenstackSubnet;
@@ -62,7 +60,6 @@
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -85,9 +82,6 @@
     protected FlowObjectiveService flowObjectiveService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected DeviceService deviceService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OpenstackInterfaceService openstackService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -96,6 +90,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ScalableGatewayService gatewayService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
     private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
     private final InternalNodeListener nodeListener = new InternalNodeListener();
@@ -111,37 +111,56 @@
 
     @Deactivate
     protected void deactivate() {
+        super.deactivate();
         nodeService.removeListener(nodeListener);
         log.info("stopped");
     }
 
     @Override
+    protected void hostDetected(Host host) {
+        // Installs forwarding flow rules to VMs in different nodes and different subnets
+        // that are connected via a router
+        Optional<OpenstackRouter> routerOfTheHost = getRouter(host);
+
+        if (!routerOfTheHost.isPresent()) {
+            return;
+        }
+
+        routableSubNets(routerOfTheHost.get().id()).stream()
+                .filter(subnet -> !subnet.id().equals(host.annotations().value(SUBNET_ID)))
+                .forEach(subnet -> setForwardingRulesAmongHostsInDifferentCnodes(host, getHosts(subnet), true));
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        // Removes forwarding flow rules to VMs in different nodes and different subnets
+        // that are connected via a router
+        Optional<OpenstackRouter> routerOfTheHost = getRouter(host);
+        if (!routerOfTheHost.isPresent()) {
+            return;
+        }
+
+        routableSubNets(routerOfTheHost.get().id()).stream()
+                .filter(subnet -> !subnet.id().equals(host.annotations().value(SUBNET_ID)))
+                .forEach(subnet -> setForwardingRulesAmongHostsInDifferentCnodes(host, getHosts(subnet), false));
+    }
+
+    @Override
     public void createRouter(OpenstackRouter osRouter) {
     }
 
     @Override
     public void updateRouter(OpenstackRouter osRouter) {
         if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
-            openstackService.ports().stream()
-                    .filter(osPort -> osPort.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
-                            osPort.deviceId().equals(osRouter.id()))
-                    .forEach(osPort -> {
-                        String subnetId = osPort.fixedIps().keySet().stream().findFirst().get();
-                        setExternalConnection(osRouter, subnetId);
-                    });
+            routableSubNets(osRouter.id()).stream()
+                    .forEach(subnet -> setExternalConnection(osRouter, subnet, true));
 
             log.info("Connected external gateway {} to router {}",
                      osRouter.gatewayExternalInfo().externalFixedIps(),
                      osRouter.name());
         } else {
-            openstackService.ports().stream()
-                    .filter(osPort -> osPort.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
-                            osPort.deviceId().equals(osRouter.id()))
-                    .forEach(osPort -> {
-                        String subnetId = osPort.fixedIps().keySet().stream().findFirst().get();
-                        OpenstackSubnet osSubNet = openstackService.subnet(subnetId);
-                        unsetExternalConnection(osRouter, osPort.networkId(), osSubNet.cidr());
-                    });
+            routableSubNets(osRouter.id()).stream()
+                    .forEach(subnet -> setExternalConnection(osRouter, subnet, false));
 
             log.info("Disconnected external gateway from router {}",
                      osRouter.name());
@@ -150,309 +169,163 @@
 
     @Override
     public void removeRouter(String osRouterId) {
-        // TODO implement this
+        // Nothing to do
+        // All interfaces need to be removed before the router is removed,
+        // and all related flow rues are removed when the interfaces are removed.
     }
 
     @Override
-    public void addRouterInterface(OpenstackRouterInterface routerIface) {
-        OpenstackRouter osRouter = openstackRouter(routerIface.id());
-        OpenstackPort osPort = openstackService.port(routerIface.portId());
-        if (osRouter == null || osPort == null) {
-            log.warn("Failed to add router interface {}", routerIface);
+    public void addRouterInterface(OpenstackRouterInterface routerIfaceAdded) {
+        OpenstackRouter osRouter = openstackRouter(routerIfaceAdded.id());
+        OpenstackSubnet osSubnetAdded = openstackService.subnet(routerIfaceAdded.subnetId());
+        if (osRouter == null || osSubnetAdded == null) {
+            log.warn("Failed to add router interface {}", routerIfaceAdded);
             return;
         }
-
-        setGatewayIcmp(Ip4Address.valueOf(openstackService.subnet(routerIface.subnetId()).gatewayIp()));
-
-        setRoutes(osRouter, Optional.empty());
-        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
-            String subnetId = osPort.fixedIps().keySet().stream().findFirst().get();
-            setExternalConnection(osRouter, subnetId);
-        }
-        log.info("Connected {} to router {}", osPort.fixedIps(), osRouter.name());
+        handleRouterInterfaces(osRouter, osSubnetAdded);
     }
 
     @Override
     public void removeRouterInterface(OpenstackRouterInterface routerIface) {
         OpenstackRouter osRouter = openstackService.router(routerIface.id());
+        OpenstackSubnet osSubnetRemoved = openstackService.subnet(routerIface.subnetId());
         if (osRouter == null) {
             log.warn("Failed to remove router interface {}", routerIface);
             return;
         }
+        handleRouterInterfacesRemoved(osRouter, osSubnetRemoved);
 
-        OpenstackSubnet osSubnet = openstackService.subnet(routerIface.subnetId());
-        OpenstackNetwork osNet = openstackService.network(osSubnet.networkId());
+        log.info("Disconnected {} from router {}", osSubnetRemoved.cidr(), osRouter.name());
+    }
 
-        unsetGatewayIcmp(Ip4Address.valueOf(openstackService.subnet(routerIface.subnetId()).gatewayIp()));
+    private void handleRouterInterfaces(OpenstackRouter osRouter, OpenstackSubnet osSubnetAdded) {
+        OpenstackNetwork osNetworkAdded = openstackService.network(osSubnetAdded.networkId());
+        if (osNetworkAdded == null) {  // in case of external network subnet
+            return;
+        }
 
-        unsetRoutes(osRouter, osSubnet);
+        // Sets flow rules for routing among subnets connected to a router.
+        setRoutesAmongSubnets(osRouter, osSubnetAdded, true);
 
+        // Sets flow rules for forwarding "packets going to external networks" to gateway nodes.
         if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
-            unsetExternalConnection(osRouter, osNet.id(), osSubnet.cidr());
-        }
-        log.info("Disconnected {} from router {}", osSubnet.cidr(), osRouter.name());
-    }
-
-    private void setGatewayIcmp(Ip4Address gatewayIp) {
-        if (gatewayIp == null) {
-            return;
-        }
-        gatewayService.getGatewayDeviceIds().forEach(deviceId -> populateGatewayIcmpRule(gatewayIp, deviceId));
-    }
-
-    private void populateGatewayIcmpRule(Ip4Address gatewayIp, DeviceId deviceId) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
-                .matchIPDst(gatewayIp.toIpPrefix());
-
-        tBuilder.setOutput(PortNumber.CONTROLLER);
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withPriority(GATEWAY_ICMP_PRIORITY)
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(deviceId, fo);
-    }
-
-    private void unsetGatewayIcmp(Ip4Address gatewayIp) {
-        if (gatewayIp == null) {
-            return;
-        }
-        gatewayService.getGatewayDeviceIds().forEach(deviceId -> {
-            removeGatewayIcmpRule(gatewayIp, deviceId);
-        });
-    }
-
-    private void removeGatewayIcmpRule(Ip4Address gatewayIp, DeviceId deviceId) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
-                .matchIPDst(gatewayIp.toIpPrefix());
-
-        RulePopulatorUtil.removeRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
-                ForwardingObjective.Flag.VERSATILE, GATEWAY_ICMP_PRIORITY);
-
-    }
-    private void setExternalConnection(OpenstackRouter osRouter, String osSubNetId) {
-        if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
-            log.debug("Source NAT is disabled");
-            return;
+            setExternalConnection(osRouter, osSubnetAdded, true);
         }
 
-        OpenstackSubnet osSubNet = openstackService.subnet(osSubNetId);
-        OpenstackNetwork osNet = openstackService.network(osSubNet.networkId());
-        populateExternalRules(osNet, osSubNet);
+        // Sets flow rules to handle ping to the virtual gateway.
+        Ip4Address vGatewayIp = Ip4Address.valueOf(osSubnetAdded.gatewayIp());
+        gatewayService.getGatewayDeviceIds()
+                .forEach(deviceId -> setGatewayIcmpRule(vGatewayIp, deviceId, true));
+
+        // Sets east-west routing rules for VMs in different Cnode to Switching Table.
+        setForwardingRulesForEastWestRouting(osRouter, osSubnetAdded, true);
+
     }
 
-    private void unsetExternalConnection(OpenstackRouter osRouter, String osNetId, String subNetCidr) {
-        if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
-            log.debug("Source NAT is disabled");
-            return;
+    private void handleRouterInterfacesRemoved(OpenstackRouter osRouter, OpenstackSubnet osSubnetRemoved) {
+
+        // Removes flow rules for routing among subnets connected to a router.
+        setRoutesAmongSubnets(osRouter, osSubnetRemoved, false);
+
+        // Removes flow rules for forwarding "packets going to external networks" to gateway nodes.
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            setExternalConnection(osRouter, osSubnetRemoved, false);
         }
 
-        // FIXME router interface is subnet specific, not network
-        OpenstackNetwork osNet = openstackService.network(osNetId);
-        removeExternalRules(osNet, subNetCidr);
+        // Removes flow rules to handle ping to the virtual gateway.
+        Ip4Address vGatewayIp = Ip4Address.valueOf(osSubnetRemoved.gatewayIp());
+        gatewayService.getGatewayDeviceIds()
+                .forEach(deviceId -> setGatewayIcmpRule(vGatewayIp, deviceId, false));
+
+        // Removes east-west routing rules for VMs in different Cnode to Switching Table.
+        setForwardingRulesForEastWestRouting(osRouter, osSubnetRemoved, false);
+
+        // Resets east-west routing rules for VMs in different Cnode to Switching Table.
+        routableSubNets(osRouter.id()).stream()
+                .forEach(subnet -> setForwardingRulesForEastWestRouting(osRouter, subnet, true));
     }
 
-    private void setRoutes(OpenstackRouter osRouter, Optional<Host> host) {
+    private void setRoutesAmongSubnets(OpenstackRouter osRouter, OpenstackSubnet osSubnetAdded, boolean install) {
         Set<OpenstackSubnet> routableSubNets = routableSubNets(osRouter.id());
         if (routableSubNets.size() < 2) {
             // no other subnet interface is connected to this router, do nothing
             return;
         }
 
-        Set<String> routableSubNetIds = routableSubNets.stream()
-                .map(OpenstackSubnet::id)
-                .collect(Collectors.toSet());
-
-        if (host.isPresent()) {
-            if (!routableSubNetIds.contains(host.get().annotations().value(SUBNET_ID))) {
-                // subnet of host is not connected to this router, do nothing.
-                return;
-            }
-        }
-
-        Set<Host> hosts = host.isPresent() ? ImmutableSet.of(host.get()) :
-                Tools.stream(hostService.getHosts())
-                        .filter(h -> routableSubNetIds.contains(h.annotations().value(SUBNET_ID)))
-                        .collect(Collectors.toSet());
-
-        hosts.forEach(h -> populateRoutingRules(h, routableSubNets));
-    }
-
-    private void unsetRoutes(OpenstackRouter osRouter, OpenstackSubnet osSubNet) {
-        Set<OpenstackSubnet> routableSubNets = routableSubNets(osRouter.id());
-        Tools.stream(hostService.getHosts())
-                .filter(h -> Objects.equals(
-                        h.annotations().value(NETWORK_ID), osSubNet.id()))
-                .forEach(h -> removeRoutingRules(h, routableSubNets));
-
-        routableSubNets.forEach(n -> {
-            Tools.stream(hostService.getHosts())
-                    .filter(h -> Objects.equals(
-                            h.annotations().value(SUBNET_ID),
-                            n.id()))
-                    .forEach(h -> removeRoutingRules(h, ImmutableSet.of(osSubNet)));
-            log.debug("Removed between {} to {}", n.name(), osSubNet.name());
-        });
-    }
-
-    private OpenstackRouter openstackRouter(String routerId) {
-        return openstackService.routers().stream().filter(r ->
-                r.id().equals(routerId)).iterator().next();
-    }
-
-    private Optional<OpenstackPort> routerIfacePort(String osNetId, String osSubNetId) {
-        // FIXME router interface is subnet specific, not network
-        return openstackService.ports().stream()
-                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
-                        p.networkId().equals(osNetId) &&
-                        p.fixedIps().containsKey(osSubNetId))
-                .findAny();
-    }
-
-    private Set<OpenstackSubnet> routableSubNets(String osRouterId) {
-        return openstackService.ports().stream()
-                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
-                        p.deviceId().equals(osRouterId))
-                .map(p -> openstackService.subnet(p.fixedIps().keySet().stream().findFirst().get()))
-                .collect(Collectors.toSet());
-    }
-
-    private void populateExternalRules(OpenstackNetwork osNet, OpenstackSubnet osSubNet) {
-        populateCnodeToGateway(Long.valueOf(osNet.segmentId()), osSubNet.cidr());
-        populateGatewayToController(Long.valueOf(osNet.segmentId()), osSubNet.cidr());
-    }
-
-    private void removeExternalRules(OpenstackNetwork osNet, String subNetCidr) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(Long.valueOf(osNet.segmentId()))
-                .matchIPSrc(IpPrefix.valueOf(subNetCidr))
-                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
-
-        nodeService.completeNodes().forEach(node -> {
-            ForwardingObjective.Flag flag = node.type().equals(GATEWAY) ?
-                    ForwardingObjective.Flag.VERSATILE :
-                    ForwardingObjective.Flag.SPECIFIC;
-
-            RulePopulatorUtil.removeRule(
-                    flowObjectiveService,
-                    appId,
-                    node.intBridge(),
-                    sBuilder.build(),
-                    flag,
-                    ROUTING_RULE_PRIORITY);
-        });
-    }
-
-    private void populateRoutingRules(Host host, Set<OpenstackSubnet> osSubNets) {
-        String osSubNetId = host.annotations().value(SUBNET_ID);
-        if (osSubNetId == null) {
-            return;
-        }
-
-        DeviceId localDevice = host.location().deviceId();
-        PortNumber localPort = host.location().port();
-        if (!nodeService.dataIp(localDevice).isPresent()) {
-            log.warn("Failed to populate L3 rules");
-            return;
-        }
-
         Map<String, String> vniMap = new HashMap<>();
         openstackService.networks().forEach(n -> vniMap.put(n.id(), n.segmentId()));
 
-        // TODO improve pipeline, do we have to install access rules between networks
-        // for every single VMs?
-        osSubNets.stream().filter(osSubNet -> !osSubNet.id().equals(osSubNetId)).forEach(osSubNet -> {
-            populateRoutingRulestoSameNode(
-                    host.ipAddresses().stream().findFirst().get().getIp4Address(),
-                    host.mac(),
-                    localPort, localDevice,
-                    Long.valueOf(vniMap.get(osSubNet.networkId())),
-                    osSubNet.cidr());
-
-            nodeService.completeNodes().stream()
-                    .filter(node -> node.type().equals(COMPUTE))
-                    .filter(node -> !node.intBridge().equals(localDevice))
-                    .forEach(node -> populateRoutingRulestoDifferentNode(
-                            host.ipAddresses().stream().findFirst().get().getIp4Address(),
-                            Long.valueOf(vniMap.get(osSubNet.networkId())),
-                            node.intBridge(),
-                            nodeService.dataIp(localDevice).get().getIp4Address(),
-                            osSubNet.cidr()));
-        });
+        routableSubNets.stream()
+                .filter(subnet -> !subnet.id().equals(osSubnetAdded.id()))
+                .filter(subnet -> vniMap.get(subnet.networkId()) != null)
+                .forEach(subnet -> nodeService.completeNodes().stream()
+                        .filter(node -> node.type().equals(COMPUTE))
+                        .forEach(node -> {
+                                setRoutingRules(node.intBridge(),
+                                        Integer.parseInt(vniMap.get(subnet.networkId())),
+                                        Integer.parseInt(vniMap.get(osSubnetAdded.networkId())),
+                                        subnet, osSubnetAdded, install);
+                                setRoutingRules(node.intBridge(),
+                                        Integer.parseInt(vniMap.get(osSubnetAdded.networkId())),
+                                        Integer.parseInt(vniMap.get(subnet.networkId())),
+                                        osSubnetAdded, subnet, install);
+                                }
+                        ));
     }
 
-    private void removeRoutingRules(Host host, Set<OpenstackSubnet> osSubNets) {
-        String osSubNetId = host.annotations().value(SUBNET_ID);
-        if (osSubNetId == null) {
-            return;
-        }
+    private void setRoutingRules(DeviceId deviceId, int srcVni, int dstVni,
+                                 OpenstackSubnet subnetSrc, OpenstackSubnet subnetDst, boolean install) {
 
-        Map<String, String> vniMap = new HashMap<>();
-        openstackService.networks().forEach(n -> vniMap.put(n.id(), n.segmentId()));
-
-        osSubNets.stream().filter(osSubNet -> !osSubNet.id().equals(osSubNetId)).forEach(osSubNet -> {
-            TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-            sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPDst(host.ipAddresses().stream().findFirst().get().toIpPrefix())
-                    .matchIPSrc(IpPrefix.valueOf(osSubNet.cidr()))
-                    .matchTunnelId(Long.valueOf(vniMap.get(osSubNet.networkId())));
-
-            nodeService.completeNodes().stream()
-                    .filter(node -> node.type().equals(COMPUTE))
-                    .forEach(node -> RulePopulatorUtil.removeRule(
-                            flowObjectiveService,
-                            appId,
-                            node.intBridge(),
-                            sBuilder.build(),
-                            ForwardingObjective.Flag.SPECIFIC,
-                            EW_ROUTING_RULE_PRIORITY));
-        });
-        log.debug("Removed routing rule from {} to {}", host, osSubNets);
-    }
-
-    private void populateGatewayToController(long vni, String subNetCidr) {
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
         sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(vni)
-                .matchIPSrc(IpPrefix.valueOf(subNetCidr))
-                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
-        tBuilder.setOutput(PortNumber.CONTROLLER);
+                .matchTunnelId(srcVni)
+                .matchIPSrc(IpPrefix.valueOf(subnetSrc.cidr()))
+                .matchIPDst(IpPrefix.valueOf(subnetDst.cidr()));
 
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(ROUTING_RULE_PRIORITY)
-                .fromApp(appId)
-                .add();
+        tBuilder.setTunnelId(dstVni);
 
-        gatewayService.getGatewayDeviceIds().forEach(deviceId -> flowObjectiveService.forward(deviceId, fo));
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, EW_ROUTING_RULE_PRIORITY, install);
+
+        // Flow rules for destination is in different subnet and different node,
+        // because VNI is converted to destination VNI in the source VM node.
+        sBuilder = DefaultTrafficSelector.builder();
+        tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(dstVni)
+                .matchIPSrc(IpPrefix.valueOf(subnetSrc.cidr()))
+                .matchIPDst(IpPrefix.valueOf(subnetDst.cidr()));
+
+        tBuilder.setTunnelId(dstVni);
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, EW_ROUTING_RULE_PRIORITY, install);
     }
 
-    private void populateCnodeToGateway(long vni, String subnetCidr) {
+    private void setExternalConnection(OpenstackRouter osRouter, OpenstackSubnet osSubNet, boolean install) {
+        if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
+            log.debug("Source NAT is disabled");
+            return;
+        }
+
+        //OpenstackSubnet osSubNet = openstackService.subnet(osSubNetId);
+        OpenstackNetwork osNet = openstackService.network(osSubNet.networkId());
+
         nodeService.completeNodes().stream()
                 .filter(node -> node.type().equals(COMPUTE))
-                .forEach(node -> populateRuleToGateway(
+                .forEach(node -> setRulesToGateway(
                         node.intBridge(),
                         gatewayService.getGatewayGroupId(node.intBridge()),
-                        vni, subnetCidr));
+                        Long.valueOf(osNet.segmentId()), osSubNet.cidr(), install));
+
+        // Is this for PNAT ??
+        setRulesForGatewayToController(Long.valueOf(osNet.segmentId()), osSubNet.cidr(), install);
     }
 
-    private void populateRuleToGateway(DeviceId deviceId, GroupId groupId, long vni, String cidr) {
+    private void setRulesToGateway(DeviceId deviceId, GroupId groupId, long vni, String cidr, boolean install) {
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
@@ -462,133 +335,119 @@
                 .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
 
         tBuilder.group(groupId);
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .withPriority(ROUTING_RULE_PRIORITY)
-                .fromApp(appId)
-                .add();
 
-        flowObjectiveService.forward(deviceId, fo);
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, ROUTING_RULE_PRIORITY, install);
     }
 
-    private void populateRoutingRulestoDifferentNode(Ip4Address vmIp, long vni,
-                                                     DeviceId deviceId, Ip4Address hostIp,
-                                                     String cidr) {
+    private void setRulesForGatewayToController(long vni, String subNetCidr, boolean install) {
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
         sBuilder.matchEthType(Ethernet.TYPE_IPV4)
                 .matchTunnelId(vni)
-                .matchIPSrc(IpPrefix.valueOf(cidr))
-                .matchIPDst(vmIp.toIpPrefix());
-        tBuilder.extension(buildExtension(deviceService, deviceId, hostIp), deviceId)
-                .setOutput(nodeService.tunnelPort(deviceId).get());
+                .matchIPSrc(IpPrefix.valueOf(subNetCidr))
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+        tBuilder.setOutput(PortNumber.CONTROLLER);
 
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withPriority(EW_ROUTING_RULE_PRIORITY)
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(deviceId, fo);
+        gatewayService.getGatewayDeviceIds().forEach(deviceId ->
+                RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                        tBuilder.build(), ForwardingObjective.Flag.VERSATILE, ROUTING_RULE_PRIORITY, install));
     }
 
-    private void populateRoutingRulestoSameNode(Ip4Address vmIp, MacAddress vmMac,
-                                                PortNumber port, DeviceId deviceId, long vni,
-                                                String cidr) {
+    private void setGatewayIcmpRule(Ip4Address gatewayIp, DeviceId deviceId, boolean install) {
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
-        // FIXME: we need to check the VNI of the dest IP also just in case...
         sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(vmIp.toIpPrefix())
-                .matchIPSrc(IpPrefix.valueOf(cidr))
-                .matchTunnelId(vni);
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIPDst(gatewayIp.toIpPrefix());
 
-        tBuilder.setEthDst(vmMac)
-                .setOutput(port);
+        tBuilder.setOutput(PortNumber.CONTROLLER);
 
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withPriority(EW_ROUTING_RULE_PRIORITY)
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(deviceId, fo);
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.VERSATILE, GATEWAY_ICMP_PRIORITY, install);
     }
 
-    private void reloadRoutingRules() {
-        eventExecutor.execute(() -> openstackService.ports().stream()
-                .filter(osPort -> osPort.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
-                .forEach(osPort -> {
-                    OpenstackRouter osRouter = openstackRouter(osPort.deviceId());
+    private void setForwardingRulesForEastWestRouting(OpenstackRouter router, OpenstackSubnet subnetAdded,
+                                                      boolean install) {
 
-                    setGatewayIcmp(Ip4Address.valueOf(openstackService
-                            .subnet(osPort.fixedIps().keySet().stream().findAny().get()).gatewayIp()));
+        Set<OpenstackSubnet> subnets = routableSubNets(router.id());
 
-                    setRoutes(osRouter, Optional.empty());
-                    if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
-                        String subnetId = osPort.fixedIps().keySet().stream().findFirst().get();
-                        setExternalConnection(osRouter, subnetId);
+        Set<Host> hosts = Tools.stream(hostService.getHosts())
+                .filter(h -> getVni(h).equals(openstackService.network(subnetAdded.networkId()).segmentId()))
+                .collect(Collectors.toSet());
+
+        subnets.stream()
+                .filter(subnet -> !subnet.id().equals(subnetAdded.id()))
+                .forEach(subnet -> getHosts(subnet)
+                        .forEach(h -> setForwardingRulesAmongHostsInDifferentCnodes(h, hosts, install)));
+    }
+
+    private void setForwardingRulesAmongHostsInDifferentCnodes(Host host, Set<Host> remoteHosts, boolean install) {
+        Ip4Address localVmIp = getIp(host);
+        DeviceId localDeviceId = host.location().deviceId();
+        Optional<IpAddress> localDataIp = nodeService.dataIp(localDeviceId);
+
+        if (!localDataIp.isPresent()) {
+            log.debug("Failed to get data IP for device {}",
+                    host.location().deviceId());
+            return;
+        }
+
+        remoteHosts.stream()
+                .filter(remoteHost -> !host.location().deviceId().equals(remoteHost.location().deviceId()))
+                .forEach(remoteVm -> {
+                    Optional<IpAddress> remoteDataIp = nodeService.dataIp(remoteVm.location().deviceId());
+                    if (remoteDataIp.isPresent()) {
+                        setVxLanFlowRule(getVni(remoteVm),
+                                localDeviceId,
+                                remoteDataIp.get().getIp4Address(),
+                                getIp(remoteVm), install);
+
+                        setVxLanFlowRule(getVni(host),
+                                remoteVm.location().deviceId(),
+                                localDataIp.get().getIp4Address(),
+                                localVmIp, install);
                     }
-                }));
+                });
     }
 
-    @Override
-    protected void hostDetected(Host host) {
-        String osNetId = host.annotations().value(NETWORK_ID);
-        String osSubNetId = host.annotations().value(SUBNET_ID);
-        Optional<OpenstackPort> routerIface = routerIfacePort(osNetId, osSubNetId);
-        if (!routerIface.isPresent()) {
+    private void setVxLanFlowRule(String vni, DeviceId deviceId, Ip4Address remoteIp,
+                                  Ip4Address vmIp, boolean install) {
+        Optional<PortNumber> tunnelPort = nodeService.tunnelPort(deviceId);
+        if (!tunnelPort.isPresent()) {
+            log.warn("Failed to get tunnel port from {}", deviceId);
             return;
         }
-        eventExecutor.execute(() -> setRoutes(
-                openstackRouter(routerIface.get().deviceId()),
-                Optional.of(host)));
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.parseLong(vni))
+                .matchIPDst(vmIp.toIpPrefix());
+        tBuilder.extension(buildExtension(deviceService, deviceId, remoteIp), deviceId)
+                .setOutput(tunnelPort.get());
+
+        RulePopulatorUtil.setRule(flowObjectiveService, appId, deviceId, sBuilder.build(),
+                tBuilder.build(), ForwardingObjective.Flag.SPECIFIC, SWITCHING_RULE_PRIORITY, install);
     }
 
-    @Override
-    protected void hostRemoved(Host host) {
-        String osNetId = host.annotations().value(NETWORK_ID);
-        String osSubNetId = host.annotations().value(SUBNET_ID);
-        Optional<OpenstackPort> routerIface = routerIfacePort(osNetId, osSubNetId);
-        if (!routerIface.isPresent()) {
-            return;
-        }
-        Set<OpenstackSubnet> routableSubNets = routableSubNets(routerIface.get().deviceId());
-        eventExecutor.execute(() -> removeRoutingRules(host, routableSubNets));
+
+    private OpenstackRouter openstackRouter(String routerId) {
+        return openstackService.routers().stream().filter(r ->
+                r.id().equals(routerId)).iterator().next();
     }
 
     @Override
     public void reinstallVmFlow(Host host) {
-        if (host == null) {
-            hostService.getHosts().forEach(h -> {
-                hostDetected(h);
-                log.info("Re-Install data plane flow of virtual machine {}", h);
-            });
-        } else {
-            hostDetected(host);
-            log.info("Re-Install data plane flow of virtual machine {}", host);
-        }
+        // TODO: implements later
     }
 
     @Override
     public void purgeVmFlow(Host host) {
-        if (host == null) {
-            hostService.getHosts().forEach(h -> {
-                hostRemoved(h);
-                log.info("Purge data plane flow of virtual machine {}", h);
-            });
-        } else {
-            hostRemoved(host);
-            log.info("Purge data plane flow of virtual machine {}", host);
-        }
+        // TODO: implements later
     }
 
     private class InternalNodeListener implements OpenstackNodeListener {
@@ -599,6 +458,7 @@
 
             switch (event.type()) {
                 case COMPLETE:
+                case INCOMPLETE:
                     log.info("COMPLETE node {} detected", node.hostname());
                     eventExecutor.execute(() -> {
                         if (node.type() == GATEWAY) {
@@ -609,23 +469,13 @@
                                     .build();
                             gatewayService.addGatewayNode(gnode);
                         }
-                        reloadRoutingRules();
                     });
+                    openstackService.routers().stream()
+                            .forEach(router -> routableSubNets(router.id()).stream()
+                                        .forEach(subnet -> handleRouterInterfaces(router, subnet)));
                     break;
                 case INIT:
                 case DEVICE_CREATED:
-                case INCOMPLETE:
-                    eventExecutor.execute(() -> {
-                        if (node.type() == GATEWAY) {
-                            GatewayNode gnode = GatewayNode.builder()
-                                    .gatewayDeviceId(node.intBridge())
-                                    .dataIpAddress(node.dataIp().getIp4Address())
-                                    .uplinkIntf(node.externalPortName().get())
-                                    .build();
-                            gatewayService.deleteGatewayNode(gnode);
-                        }
-                        reloadRoutingRules();
-                    });
                 default:
                     break;
             }