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

Change-Id: I6e582fff781c2e69fb6ef4b34d8e41767202fc20
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/AbstractVmHandler.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/AbstractVmHandler.java
index 58b89a6..a373ff0 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/AbstractVmHandler.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/AbstractVmHandler.java
@@ -25,6 +25,9 @@
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackRouter;
+import org.onosproject.openstackinterface.OpenstackSubnet;
 import org.slf4j.Logger;
 
 import java.util.Objects;
@@ -50,6 +53,7 @@
     protected CoreService coreService;
     protected MastershipService mastershipService;
     protected HostService hostService;
+    protected OpenstackInterfaceService openstackService;
 
     protected HostListener hostListener = new InternalHostListener();
 
@@ -57,6 +61,7 @@
         ServiceDirectory services = new DefaultServiceDirectory();
         coreService = services.get(CoreService.class);
         mastershipService = services.get(MastershipService.class);
+        openstackService = services.get(OpenstackInterfaceService.class);
         hostService = services.get(HostService.class);
         hostService.addListener(hostListener);
 
@@ -107,6 +112,28 @@
                 .findFirst();
     }
 
+    protected Set<Host> getHosts(OpenstackSubnet osSubnet) {
+        return Tools.stream(hostService.getHosts())
+                .filter(host -> host.annotations().value(SUBNET_ID).equals(osSubnet.id()))
+                .collect(Collectors.toSet());
+    }
+
+    protected Optional<OpenstackRouter> getRouter(Host host) {
+        return openstackService.routers().stream()
+                .filter(router -> routableSubNets(router.id()).stream()
+                        .filter(subnet -> subnet.id().equals(host.annotations().value(SUBNET_ID)))
+                        .findAny().isPresent())
+                .findAny();
+    }
+
+    protected 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());
+    }
+
     protected Ip4Address getIp(Host host) {
         return host.ipAddresses().stream().findFirst().get().getIp4Address();
     }
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/RulePopulatorUtil.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/RulePopulatorUtil.java
index d220b8c..3326417 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/RulePopulatorUtil.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/RulePopulatorUtil.java
@@ -21,8 +21,8 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.instructions.ExtensionPropertyException;
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
@@ -74,29 +74,36 @@
     }
 
     /**
-     * Removes flow rules with the supplied information.
+     * Adds flow rules with the supplied information.
      *
      * @param flowObjectiveService flow objective service
      * @param appId application id
      * @param deviceId device id to remove this flow rule
      * @param selector traffic selector
+     * @param treatment traffic treatment
      * @param flag flag
      * @param priority priority
+     * @param install populate flows if true, remove them otherwise
      */
-    public static void removeRule(FlowObjectiveService flowObjectiveService,
-                           ApplicationId appId,
-                           DeviceId deviceId,
-                           TrafficSelector selector,
-                           ForwardingObjective.Flag flag,
-                           int priority) {
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
+    public static void setRule(FlowObjectiveService flowObjectiveService,
+                               ApplicationId appId,
+                               DeviceId deviceId,
+                               TrafficSelector selector,
+                               TrafficTreatment treatment,
+                               ForwardingObjective.Flag flag,
+                               int priority,
+                               boolean install) {
+        ForwardingObjective.Builder foBuilder = DefaultForwardingObjective.builder()
                 .withSelector(selector)
-                .withTreatment(DefaultTrafficTreatment.builder().build())
+                .withTreatment(treatment)
                 .withFlag(flag)
                 .withPriority(priority)
-                .fromApp(appId)
-                .remove();
+                .fromApp(appId);
 
-        flowObjectiveService.forward(deviceId, fo);
+        if (install) {
+            flowObjectiveService.forward(deviceId, foBuilder.add());
+        } else {
+            flowObjectiveService.forward(deviceId, foBuilder.remove());
+        }
     }
 }
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java
index b3990e1..7b7f778 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java
@@ -263,29 +263,32 @@
                     .matchIPDst(floatingIp.toIpPrefix())
                     .matchInPort(nodeService.tunnelPort(deviceId).get());
 
-            RulePopulatorUtil.removeRule(
+            RulePopulatorUtil.setRule(
                     flowObjectiveService,
                     appId,
                     deviceId,
                     sOutgoingBuilder.build(),
+                    DefaultTrafficTreatment.builder().build(),
                     ForwardingObjective.Flag.VERSATILE,
-                    FLOATING_RULE_PRIORITY);
+                    FLOATING_RULE_PRIORITY, false);
 
-            RulePopulatorUtil.removeRule(
+            RulePopulatorUtil.setRule(
                     flowObjectiveService,
                     appId,
                     deviceId,
                     sIncomingBuilder.build(),
+                    DefaultTrafficTreatment.builder().build(),
                     ForwardingObjective.Flag.VERSATILE,
-                    FLOATING_RULE_PRIORITY);
+                    FLOATING_RULE_PRIORITY, false);
 
-            RulePopulatorUtil.removeRule(
+            RulePopulatorUtil.setRule(
                     flowObjectiveService,
                     appId,
                     deviceId,
                     sForTrafficFromVmBuilder.build(),
+                    DefaultTrafficTreatment.builder().build(),
                     ForwardingObjective.Flag.VERSATILE,
-                    FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY);
+                    FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY, false);
         });
     }
 
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;
             }
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java
index db324d7..b66ffa2 100644
--- a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java
@@ -119,7 +119,11 @@
                 .matchIPDst(getIp(host).toIpPrefix())
                 .matchTunnelId(Long.valueOf(getVni(host)));
 
-        tBuilder.setOutput(host.location().port());
+        // Destination setting is required for routing cases.
+        // We do not believe the rule would not degrade the forwarding performance.
+        // But, if it does, we need to move the rule in a separate routing table.
+        tBuilder.setEthDst(host.mac())
+                .setOutput(host.location().port());
 
         ForwardingObjective fo = DefaultForwardingObjective.builder()
                 .withSelector(sBuilder.build())
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OpenstackPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OpenstackPipeline.java
index 038b949..91e5b12 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OpenstackPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OpenstackPipeline.java
@@ -17,6 +17,7 @@
 
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.net.DeviceId;
@@ -33,7 +34,11 @@
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.TunnelIdCriterion;
 import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveStore;
 import org.onosproject.net.flowobjective.ForwardingObjective;
@@ -42,9 +47,7 @@
 import org.onosproject.net.flowobjective.ObjectiveError;
 import org.slf4j.Logger;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
+import java.util.Optional;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -62,14 +65,20 @@
     protected ApplicationId appId;
     protected FlowRuleService flowRuleService;
 
-    protected static final int VNI_TABLE = 0;
-    protected static final int FORWARDING_TABLE = 1;
-    protected static final int ACL_TABLE = 2;
+    protected static final int SRC_VNI_TABLE = 0;
+    protected static final int ACL_TABLE = 1;
+    protected static final int CT_TABLE = 2;
+    protected static final int JUMP_TABLE = 3;
+    protected static final int ROUTING_TABLE = 4;
+    protected static final int FORWARDING_TABLE = 5;
+    protected static final int DUMMY_TABLE = 10;
+    protected static final int LAST_TABLE = FORWARDING_TABLE;
 
     private static final int DROP_PRIORITY = 0;
+    private static final int HIGH_PRIORITY = 30000;
     private static final int TIME_OUT = 0;
     private static final int DHCP_SERVER_PORT = 67;
-    private static final int DHCP_CLIENT_PORT = 68;
+    private static final String VIRTUAL_GATEWAY_MAC = "fe:00:00:00:00:02";
 
 
     @Override
@@ -100,102 +109,110 @@
 
     @Override
     public void forward(ForwardingObjective forwardingObjective) {
-        Collection<FlowRule> rules;
-        FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();
+        FlowRule flowRule;
 
-        rules = processForward(forwardingObjective);
-
-        switch (forwardingObjective.op()) {
-            case ADD:
-                rules.stream()
-                        .filter(Objects::nonNull)
-                        .forEach(flowOpsBuilder::add);
+        switch (forwardingObjective.flag()) {
+            case SPECIFIC:
+                flowRule = processSpecific(forwardingObjective);
                 break;
-            case REMOVE:
-                rules.stream()
-                        .filter(Objects::nonNull)
-                        .forEach(flowOpsBuilder::remove);
+            case VERSATILE:
+                flowRule = processVersatile(forwardingObjective);
                 break;
             default:
                 fail(forwardingObjective, ObjectiveError.UNKNOWN);
-                log.warn("Unknown forwarding type {}");
+                log.warn("Unknown forwarding flag {}", forwardingObjective.flag());
+                return;
         }
 
-        flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                pass(forwardingObjective);
-            }
+        if (forwardingObjective.op().equals(Objective.Operation.ADD)) {
+            applyRules(true, flowRule);
+        } else {
+            applyRules(false, flowRule);
+        }
 
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                fail(forwardingObjective, ObjectiveError.FLOWINSTALLATIONFAILED);
-            }
-        }));
     }
 
     private void initializePipeline() {
-        processVniTable(true);
-        processForwardingTable(true);
-        processAclTable(true);
+        connectTables(SRC_VNI_TABLE, ACL_TABLE); // Table 0 -> Table 1
+        //FIXME CT table needs to be reconstructed using OVS 2.5 connection tracking feature.
+        connectTables(CT_TABLE, JUMP_TABLE);  // Table 2 -> Table 3
+        setUpTableMissEntry(ACL_TABLE);
+        setupJumpTable();
     }
 
-    private void processVniTable(boolean install) {
+    private void connectTables(int fromTable, int toTable) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
 
+        treatment.transition(toTable);
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(fromTable)
+                .build();
+
+        applyRules(true, flowRule);
+    }
+
+    private void setUpTableMissEntry(int table) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+        treatment.drop();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(table)
+                .build();
+
+        applyRules(true, flowRule);
+    }
+
+    private void setupJumpTable() {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+        selector.matchEthDst(MacAddress.valueOf(VIRTUAL_GATEWAY_MAC));
+        treatment.transition(ROUTING_TABLE);
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(HIGH_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
+                .forTable(JUMP_TABLE)
+                .build();
+
+        applyRules(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder();
+        treatment = DefaultTrafficTreatment.builder();
+
         treatment.transition(FORWARDING_TABLE);
 
-        FlowRule flowRule = DefaultFlowRule.builder()
+        flowRule = DefaultFlowRule.builder()
                 .forDevice(deviceId)
                 .withSelector(selector.build())
                 .withTreatment(treatment.build())
                 .withPriority(DROP_PRIORITY)
                 .fromApp(appId)
                 .makePermanent()
-                .forTable(VNI_TABLE)
+                .forTable(JUMP_TABLE)
                 .build();
 
-        applyRules(install, flowRule);
-    }
-
-    private void processForwardingTable(boolean install) {
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-
-        treatment.drop();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(DROP_PRIORITY)
-                .fromApp(appId)
-                .makePermanent()
-                .forTable(FORWARDING_TABLE)
-                .build();
-
-        applyRules(install, flowRule);
-    }
-
-    private void processAclTable(boolean install) {
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-
-        treatment.wipeDeferred();
-        treatment.drop();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(DROP_PRIORITY)
-                .fromApp(appId)
-                .makePermanent()
-                .forTable(ACL_TABLE)
-                .build();
-
-        applyRules(install, flowRule);
+        applyRules(true, flowRule);
     }
 
     private void applyRules(boolean install, FlowRule flowRule) {
@@ -216,20 +233,7 @@
         }));
     }
 
-    private Collection<FlowRule> processForward(ForwardingObjective forwardingObjective) {
-        switch (forwardingObjective.flag()) {
-            case SPECIFIC:
-                return processSpecific(forwardingObjective);
-            case VERSATILE:
-                return processVersatile(forwardingObjective);
-            default:
-                fail(forwardingObjective, ObjectiveError.UNKNOWN);
-                log.warn("Unknown forwarding flag {}", forwardingObjective.flag());
-        }
-        return Collections.emptySet();
-    }
-
-    private Collection<FlowRule> processVersatile(ForwardingObjective forwardingObjective) {
+    private FlowRule processVersatile(ForwardingObjective forwardingObjective) {
         log.debug("Processing versatile forwarding objective");
 
         FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
@@ -253,25 +257,39 @@
         if (ethCriterion != null) {
             if (ethCriterion.ethType().toShort() == Ethernet.TYPE_ARP ||
                     ethCriterion.ethType().toShort() == Ethernet.TYPE_LLDP) {
-                ruleBuilder.forTable(VNI_TABLE);
-                return Collections.singletonList(ruleBuilder.build());
+                ruleBuilder.forTable(SRC_VNI_TABLE);
+                return ruleBuilder.build();
             } else if (udpPortCriterion != null && udpPortCriterion.udpPort().toInt() == DHCP_SERVER_PORT) {
-                ruleBuilder.forTable(VNI_TABLE);
-                return Collections.singletonList(ruleBuilder.build());
+                ruleBuilder.forTable(SRC_VNI_TABLE);
+                return ruleBuilder.build();
             }
         }
-        return Collections.emptySet();
+
+        return null;
     }
 
-    private Collection<FlowRule> processSpecific(ForwardingObjective forwardingObjective) {
+    private FlowRule processSpecific(ForwardingObjective forwardingObjective) {
         log.debug("Processing specific forwarding objective");
 
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+
+        Optional<Instruction> group = forwardingObjective.treatment().immediate().stream()
+                .filter(i -> i.type() == Instruction.Type.GROUP).findAny();
+        int tableType = tableType(forwardingObjective);
+        if (tableType != LAST_TABLE && !group.isPresent()) {
+            treatment.transition(nextTable(tableType));
+        }
+        forwardingObjective.treatment().allInstructions().stream()
+                .filter(i -> i.type() != Instruction.Type.NOACTION).forEach(treatment::add);
+
         FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
                 .forDevice(deviceId)
                 .withSelector(forwardingObjective.selector())
-                .withTreatment(forwardingObjective.treatment())
+                .withTreatment(treatment.build())
                 .withPriority(forwardingObjective.priority())
-                .fromApp(forwardingObjective.appId());
+                .fromApp(forwardingObjective.appId())
+                .forTable(tableType);
 
         if (forwardingObjective.permanent()) {
             ruleBuilder.makePermanent();
@@ -279,30 +297,41 @@
             ruleBuilder.makeTemporary(TIME_OUT);
         }
 
-        //VNI Table Rule
-        if (forwardingObjective.selector().getCriterion(Criterion.Type.IN_PORT) != null) {
-            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-            forwardingObjective.treatment().allInstructions().forEach(tBuilder::add);
-            tBuilder.transition(FORWARDING_TABLE);
-            ruleBuilder.withTreatment(tBuilder.build());
-            ruleBuilder.forTable(VNI_TABLE);
-        } else if (forwardingObjective.selector().getCriterion(Criterion.Type.TUNNEL_ID) != null) {
-            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-            tBuilder.deferred();
-            forwardingObjective.treatment().allInstructions().forEach(tBuilder::add);
-            tBuilder.transition(ACL_TABLE);
-            ruleBuilder.withTreatment(tBuilder.build());
-            ruleBuilder.forTable(FORWARDING_TABLE);
-        } else {
-            ruleBuilder.forTable(ACL_TABLE);
-        }
-
-        return Collections.singletonList(ruleBuilder.build());
+        return ruleBuilder.build();
     }
 
+    int tableType(ForwardingObjective fo) {
 
-    private void pass(Objective obj) {
-        obj.context().ifPresent(context -> context.onSuccess(obj));
+        IPCriterion ipSrc = (IPCriterion) fo.selector().getCriterion(Criterion.Type.IPV4_SRC);
+        IPCriterion ipDst = (IPCriterion) fo.selector().getCriterion(Criterion.Type.IPV4_DST);
+        TunnelIdCriterion tunnelId =
+                (TunnelIdCriterion) fo.selector().getCriterion(Criterion.Type.TUNNEL_ID);
+        PortCriterion inPort = (PortCriterion) fo.selector().getCriterion(Criterion.Type.IN_PORT);
+        Optional<Instruction> output = fo.treatment().immediate().stream()
+                .filter(i -> i.type() == Instruction.Type.OUTPUT).findAny();
+        Optional<Instruction> group = fo.treatment().immediate().stream()
+                .filter(i -> i.type() == Instruction.Type.GROUP).findAny();
+
+        // TODO: Add the Connection Tracking Table
+        if (inPort != null) {
+            return SRC_VNI_TABLE;
+        } else if (output.isPresent()) {
+            return FORWARDING_TABLE;
+        } else if ((ipSrc != null && ipSrc.ip().prefixLength() == 32 &&
+                ipDst != null && ipDst.ip().prefixLength() == 32) ||
+                (ipSrc != null && ipSrc.ip().prefixLength() == 32 && ipDst == null) ||
+                (ipDst != null && ipDst.ip().prefixLength() == 32 && ipSrc == null)) {
+            return ACL_TABLE;
+        } else if ((tunnelId != null && ipSrc != null && ipDst != null) || group.isPresent()) {
+            return ROUTING_TABLE;
+        }
+
+        return DUMMY_TABLE;
+    }
+
+    int nextTable(int baseTable) {
+
+        return baseTable + 1;
     }
 
     private void fail(Objective obj, ObjectiveError error) {