Refactored to handle service instance by service type

- Added service instance handler
- Implemented dummy, vsg, and olt agent instance handler

Change-Id: Id3edd5eecb1caadf0f835cb10a952100e18b283b
diff --git a/apps/cordvtn/BUCK b/apps/cordvtn/BUCK
index 07e1fc0..2a7d8ca 100644
--- a/apps/cordvtn/BUCK
+++ b/apps/cordvtn/BUCK
@@ -11,6 +11,7 @@
     '//core/store/serializers:onos-core-serializers',
     '//apps/dhcp/api:onos-apps-dhcp-api',
     '//apps/xosclient:onos-apps-xosclient',
+    '//apps/cordconfig:onos-apps-cordconfig',
     '//protocols/ovsdb/api:onos-protocols-ovsdb-api',
     '//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
 ]
@@ -35,5 +36,5 @@
     included_bundles = BUNDLES,
     excluded_bundles = EXCLUDED_BUNDLES,
     description = 'APIs for interacting with the CORD VTN application.',
-    required_apps = [ 'org.onosproject.xosclient', 'org.onosproject.dhcp', 'org.onosproject.ovsdb' ],
+    required_apps = [ 'org.onosproject.cord-config', 'org.onosproject.xosclient', 'org.onosproject.dhcp', 'org.onosproject.ovsdb' ],
 )
diff --git a/apps/cordvtn/app.xml b/apps/cordvtn/app.xml
index 7e8f409..17583ce 100644
--- a/apps/cordvtn/app.xml
+++ b/apps/cordvtn/app.xml
@@ -18,7 +18,7 @@
         category="Traffic Steering" url="http://onosproject.org" title="CORD Virtual Tenant Network"
         featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
         features="${project.artifactId}"
-        apps="org.onosproject.ovsdb-base,org.onosproject.dhcp,org.onosproject.xosclient">
+        apps="org.onosproject.ovsdb-base,org.onosproject.dhcp,org.onosproject.xosclient,org.onosproject.cord-config">
     <description>${project.description}</description>
     <artifact>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</artifact>
 </app>
diff --git a/apps/cordvtn/pom.xml b/apps/cordvtn/pom.xml
index f2338f8..fa6e068 100644
--- a/apps/cordvtn/pom.xml
+++ b/apps/cordvtn/pom.xml
@@ -108,6 +108,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cord-config</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>com.jcraft</groupId>
             <artifactId>jsch</artifactId>
             <version>0.1.53</version>
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java
index b50d6fc..be097ce 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Sets;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onosproject.core.ApplicationId;
@@ -46,6 +47,7 @@
     public static final String GATEWAY_IP = "gatewayIp";
     public static final String GATEWAY_MAC = "gatewayMac";
     public static final String LOCAL_MANAGEMENT_IP = "localManagementIp";
+    public static final String MANAGEMENT_IP = "managementIpRange";
     public static final String OVSDB_PORT = "ovsdbPort";
 
     public static final String CORDVTN_NODES = "nodes";
@@ -187,6 +189,25 @@
     }
 
     /**
+     * Returns management IP address range.
+     *
+     * @return management network ip prefix, or null
+     */
+    public IpPrefix managementIpRange() {
+        JsonNode jsonNode = object.get(MANAGEMENT_IP);
+        if (jsonNode == null) {
+            return null;
+        }
+
+        try {
+            return IpPrefix.valueOf(jsonNode.asText());
+        } catch (IllegalArgumentException e) {
+            log.error("{}:{} wrong address format", MANAGEMENT_IP, jsonNode);
+            return null;
+        }
+    }
+
+    /**
      * Returns XOS access information.
      *
      * @return XOS access, or null
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java
index bd25b0f..1a8849e 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java
@@ -15,14 +15,8 @@
  */
 package org.onosproject.cordvtn.api;
 
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.HostId;
 import org.onosproject.xosclient.api.VtnServiceId;
 
-import java.util.Map;
-
 /**
  * Service for provisioning overlay virtual networks on compute nodes.
  */
@@ -31,21 +25,6 @@
     String CORDVTN_APP_ID = "org.onosproject.cordvtn";
 
     /**
-     * Adds a new VM on a given node and connect point.
-     *
-     * @param node cordvtn node
-     * @param connectPoint connect point
-     */
-    void addServiceVm(CordVtnNode node, ConnectPoint connectPoint);
-
-    /**
-     * Removes a VM from a given node and connect point.
-     *
-     * @param connectPoint connect point
-     */
-    void removeServiceVm(ConnectPoint connectPoint);
-
-    /**
      * Creates dependencies for a given tenant service.
      *
      * @param tServiceId id of the service which has a dependency
@@ -62,14 +41,4 @@
      * @param pServiceId id of the service which provide dependency
      */
     void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId);
-
-    /**
-     * Updates virtual service gateways.
-     *
-     * @param vSgHost host id of vSG host
-     * @param serviceVlan service vlan id
-     * @param vSgs map of ip and mac address of vSGs running in this vSG host
-     */
-    void updateVirtualSubscriberGateways(HostId vSgHost, String serviceVlan,
-                                         Map<IpAddress, MacAddress> vSgs);
 }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/Instance.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/Instance.java
new file mode 100644
index 0000000..83c7c08
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/Instance.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import com.google.common.base.Strings;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.xosclient.api.VtnPortId;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceId;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Provides methods to help to handle network service instance.
+ */
+public final class Instance {
+
+    public static final String SERVICE_ID = "serviceId";
+    public static final String SERVICE_TYPE = "serviceType";
+    public static final String PORT_ID = "vtnPortId";
+    public static final String CREATE_TIME = "createTime";
+    public static final String NESTED_INSTANCE = "nestedInstance";
+    public static final String TRUE = "true";
+
+    private final Host host;
+
+    /**
+     * Default constructor.
+     *
+     * @param instance host object of this instance
+     */
+    private Instance(Host instance) {
+        this.host = instance;
+    }
+
+    /**
+     * Returns host object of this instance.
+     *
+     * @return host
+     */
+    public Host host() {
+        return this.host;
+    }
+
+    /**
+     * Returns new instance.
+     *
+     * @param host host object of this instance
+     * @return instance
+     */
+    public static Instance of(Host host) {
+        checkNotNull(host);
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(SERVICE_ID)));
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(SERVICE_TYPE)));
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(PORT_ID)));
+        checkArgument(!Strings.isNullOrEmpty(host.annotations().value(CREATE_TIME)));
+
+        return new Instance(host);
+    }
+
+    /**
+     * Returns service ID of a given host.
+     *
+     * @return vtn service id
+     */
+    public VtnServiceId serviceId() {
+        String serviceId = host.annotations().value(SERVICE_ID);
+        return VtnServiceId.of(serviceId);
+    }
+
+    /**
+     * Returns service type of a given host.
+     *
+     * @return vtn service type
+     */
+    public VtnService.ServiceType serviceType() {
+        String serviceType = host.annotations().value(SERVICE_TYPE);
+        return VtnService.ServiceType.valueOf(serviceType);
+    }
+
+    /**
+     * Returns port ID of a given host.
+     *
+     * @return vtn port id
+     */
+    public VtnPortId portId() {
+        String portId = host.annotations().value(PORT_ID);
+        return VtnPortId.of(portId);
+    }
+
+    /**
+     * Returns if the instance is nested container or not.
+     *
+     * @return true if it's nested container; false otherwise
+     */
+    public boolean isNestedInstance() {
+        return host.annotations().value(NESTED_INSTANCE) != null;
+    }
+
+    /**
+     * Returns MAC address of this instance.
+     *
+     * @return mac address
+     */
+    public MacAddress mac() {
+        return host.mac();
+    }
+
+    /**
+     * Returns IP address of this instance.
+     *
+     * @return ip address
+     */
+    public Ip4Address ipAddress() {
+        // assume all instance has only one IP address, and only IP4 is supported now
+        return host.ipAddresses().stream().findFirst().get().getIp4Address();
+    }
+
+    /**
+     * Returns device ID of this host.
+     *
+     * @return device id
+     */
+    public DeviceId deviceId() {
+        return host.location().deviceId();
+    }
+
+    /**
+     * Returns the port number where this host is.
+     *
+     * @return port number
+     */
+    public PortNumber portNumber() {
+        return host.location().port();
+    }
+
+    /**
+     * Returns annotation value with a given key.
+     *
+     * @param annotationKey annotation key
+     * @return annotation value
+     */
+    public String getAnnotation(String annotationKey) {
+        return host.annotations().value(annotationKey);
+    }
+
+    @Override
+    public String toString() {
+        return host.toString();
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/InstanceHandler.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/InstanceHandler.java
new file mode 100644
index 0000000..3e5be2f
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/api/InstanceHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+/**
+ * Handles service instance detection and removal.
+ */
+public interface InstanceHandler {
+
+    /**
+     * Handles newly detected instance.
+     *
+     * @param instance instance
+     */
+    void instanceDetected(Instance instance);
+
+    /**
+     * Handles removed instance.
+     *
+     * @param instance instance
+     */
+    void instanceRemoved(Instance instance);
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java
index f428ea4..228d06c 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java
@@ -18,7 +18,7 @@
 
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.cordvtn.impl.CordVtnNodeManager;
+import org.onosproject.cordvtn.impl.CordVtnPipeline;
 
 /**
  * Deletes nodes from the service.
@@ -29,8 +29,8 @@
 
     @Override
     protected void execute() {
-        CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
-        nodeManager.flushRules();
+        CordVtnPipeline pipeline = AbstractShellCommand.get(CordVtnPipeline.class);
+        pipeline.flushRules();
         print("Successfully flushed");
     }
 }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
index cfcdc4b..efa0e4a 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.cordvtn.impl;
 
-import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -27,210 +26,77 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onlab.packet.Ip4Prefix;
 import org.onosproject.cordvtn.api.CordVtnNode;
 import org.onosproject.cordvtn.api.CordVtnService;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.CoreService;
-import org.onosproject.dhcp.DhcpService;
-import org.onosproject.mastership.MastershipService;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
-import org.onosproject.net.HostId;
-import org.onosproject.net.HostLocation;
-import org.onosproject.net.Port;
-import org.onosproject.net.config.ConfigFactory;
-import org.onosproject.net.config.NetworkConfigEvent;
-import org.onosproject.net.config.NetworkConfigListener;
-import org.onosproject.net.config.NetworkConfigRegistry;
-import org.onosproject.net.config.basics.SubjectFactories;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
 import org.onosproject.net.group.GroupService;
-import org.onosproject.net.host.DefaultHostDescription;
-import org.onosproject.net.host.HostDescription;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
-import org.onosproject.net.host.HostProvider;
-import org.onosproject.net.host.HostProviderRegistry;
-import org.onosproject.net.host.HostProviderService;
-import org.onosproject.net.host.HostService;
-import org.onosproject.net.packet.PacketContext;
-import org.onosproject.net.packet.PacketProcessor;
-import org.onosproject.net.packet.PacketService;
-import org.onosproject.net.provider.AbstractProvider;
-import org.onosproject.net.provider.ProviderId;
-import org.onosproject.xosclient.api.OpenStackAccess;
-import org.onosproject.xosclient.api.VtnPort;
-import org.onosproject.xosclient.api.VtnPortApi;
-import org.onosproject.xosclient.api.VtnPortId;
 import org.onosproject.xosclient.api.VtnService;
-import org.onosproject.xosclient.api.VtnServiceApi;
 import org.onosproject.xosclient.api.VtnServiceId;
-import org.onosproject.xosclient.api.XosAccess;
-import org.onosproject.xosclient.api.XosClientService;
 import org.slf4j.Logger;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
+import static org.onosproject.net.group.DefaultGroupBucket.createSelectGroupBucket;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Provisions virtual tenant networks with service chaining capability
- * in OpenStack environment.
+ * Provisions service dependency capabilities between network services.
  */
 @Component(immediate = true)
 @Service
-public class CordVtn extends AbstractProvider implements CordVtnService, HostProvider {
+public class CordVtn extends CordVtnInstanceHandler implements CordVtnService {
 
     protected final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CoreService coreService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected NetworkConfigRegistry configRegistry;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostProviderRegistry hostProviderRegistry;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected DeviceService deviceService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostService hostService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected FlowRuleService flowRuleService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected PacketService packetService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MastershipService mastershipService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected GroupService groupService;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected DhcpService dhcpService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected XosClientService xosClient;
-
-    private final ConfigFactory configFactory =
-            new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
-                @Override
-                public CordVtnConfig createConfig() {
-                    return new CordVtnConfig();
-                }
-            };
-
-    private static final String XOS_ACCESS_ERROR = "XOS access is not configured";
-    private static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
-
-    private static final String DEFAULT_TUNNEL = "vxlan";
-    private static final String SERVICE_ID = "serviceId";
-    private static final String PORT_ID = "vtnPortId";
-    private static final String DATA_PLANE_IP = "dataPlaneIp";
-    private static final String DATA_PLANE_INTF = "dataPlaneIntf";
-    private static final String S_TAG = "stag";
-    private static final String VSG_HOST_ID = "vsgHostId";
-    private static final String CREATE_TIME = "createTime";
-
-    private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
-
-    private final ExecutorService eventExecutor =
-            newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
-
-    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
-    private final HostListener hostListener = new InternalHostListener();
-    private final NetworkConfigListener configListener = new InternalConfigListener();
-
-    private ApplicationId appId;
-    private HostProviderService hostProvider;
-    private CordVtnRuleInstaller ruleInstaller;
-    private CordVtnArpProxy arpProxy;
-
-    private volatile XosAccess xosAccess = null;
-    private volatile OpenStackAccess osAccess = null;
-    private volatile MacAddress privateGatewayMac = MacAddress.NONE;
-
-    /**
-     * Creates an cordvtn host location provider.
-     */
-    public CordVtn() {
-        super(new ProviderId("host", CORDVTN_APP_ID));
-    }
-
     @Activate
     protected void activate() {
-        appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
-        ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
-                                                 deviceService,
-                                                 groupService,
-                                                 hostService,
-                                                 configRegistry,
-                                                 DEFAULT_TUNNEL);
-
-        arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
-        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
-        arpProxy.requestPacket();
-
-        hostService.addListener(hostListener);
-        hostProvider = hostProviderRegistry.register(this);
-
-        configRegistry.registerConfigFactory(configFactory);
-        configRegistry.addListener(configListener);
-
-        log.info("Started");
+        eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
+        hostListener = new InternalHostListener();
+        super.activate();
     }
 
     @Deactivate
     protected void deactivate() {
-        hostProviderRegistry.unregister(this);
-        hostService.removeListener(hostListener);
-
-        packetService.removeProcessor(packetProcessor);
-
-        configRegistry.unregisterConfigFactory(configFactory);
-        configRegistry.removeListener(configListener);
-
-        eventExecutor.shutdown();
-        log.info("Stopped");
-    }
-
-    @Override
-    public void triggerProbe(Host host) {
-        /*
-         * Note: In CORD deployment, we assume that all hosts are configured.
-         * Therefore no probe is required.
-         */
+        super.deactivate();
     }
 
     @Override
     public void createServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId,
                                         boolean isBidirectional) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        // TODO remove openstack access when XOS provides all information
-        VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
-        VtnService tService = serviceApi.service(tServiceId, osAccess);
-        VtnService pService = serviceApi.service(pServiceId, osAccess);
+        VtnService tService = getVtnService(tServiceId);
+        VtnService pService = getVtnService(pServiceId);
 
         if (tService == null || pService == null) {
             log.error("Failed to create dependency between {} and {}",
@@ -239,18 +105,13 @@
         }
 
         log.info("Created dependency between {} and {}", tService.name(), pService.name());
-        ruleInstaller.populateServiceDependencyRules(tService, pService, isBidirectional, true);
+        serviceDependencyRules(tService, pService, isBidirectional, true);
     }
 
     @Override
     public void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        // TODO remove openstack access when XOS provides all information
-        VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
-        VtnService tService = serviceApi.service(tServiceId, osAccess);
-        VtnService pService = serviceApi.service(pServiceId, osAccess);
+        VtnService tService = getVtnService(tServiceId);
+        VtnService pService = getVtnService(pServiceId);
 
         if (tService == null || pService == null) {
             log.error("Failed to remove dependency between {} and {}",
@@ -259,427 +120,267 @@
         }
 
         log.info("Removed dependency between {} and {}", tService.name(), pService.name());
-        ruleInstaller.populateServiceDependencyRules(tService, pService, true, false);
+        serviceDependencyRules(tService, pService, true, false);
     }
 
     @Override
-    public void addServiceVm(CordVtnNode node, ConnectPoint connectPoint) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
-        String portName = port.annotations().value("portName");
-
-        // TODO remove openstack access when XOS provides all information
-        VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
-        VtnPort vtnPort = portApi.vtnPort(portName, osAccess);
-        if (vtnPort == null) {
-            log.warn("Failed to get port information of {}", portName);
+    public void instanceDetected(Instance instance) {
+        VtnService service = getVtnService(instance.serviceId());
+        if (service == null) {
             return;
         }
 
-        // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
-        // existing instances.
-        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
-                .set(SERVICE_ID, vtnPort.serviceId().id())
-                .set(PORT_ID, vtnPort.id().id())
-                .set(DATA_PLANE_IP, node.dpIp().ip().toString())
-                .set(DATA_PLANE_INTF, node.dpIntf())
-                .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+        // TODO get bidirectional information from XOS once XOS supports
+        service.tenantServices().stream().forEach(
+                tServiceId -> createServiceDependency(tServiceId, service.id(), true));
+        service.providerServices().stream().forEach(
+                pServiceId -> createServiceDependency(service.id(), pServiceId, true));
 
-        // TODO address service specific task in a separate package
-        String serviceVlan = getServiceVlan(vtnPort);
-        if (!Strings.isNullOrEmpty(serviceVlan)) {
-            annotations.set(S_TAG, serviceVlan);
-        }
-
-        HostDescription hostDesc = new DefaultHostDescription(
-                vtnPort.mac(),
-                VlanId.NONE,
-                new HostLocation(connectPoint, System.currentTimeMillis()),
-                Sets.newHashSet(vtnPort.ip()),
-                annotations.build());
-
-        HostId hostId = HostId.hostId(vtnPort.mac());
-        hostProvider.hostDetected(hostId, hostDesc, false);
+        updateProviderServiceInstances(service);
     }
 
     @Override
-    public void removeServiceVm(ConnectPoint connectPoint) {
-        hostService.getConnectedHosts(connectPoint)
-                .stream()
-                .forEach(host -> hostProvider.hostVanished(host.id()));
-    }
-
-    @Override
-    // TODO address service specific task in a separate package
-    public void updateVirtualSubscriberGateways(HostId vSgHostId, String serviceVlan,
-                                                Map<IpAddress, MacAddress> vSgs) {
-        Host vSgHost = hostService.getHost(vSgHostId);
-        if (vSgHost == null || !vSgHost.annotations().value(S_TAG).equals(serviceVlan)) {
-            log.debug("Invalid vSG updates for {}", serviceVlan);
+    public void instanceRemoved(Instance instance) {
+        VtnService service = getVtnService(instance.serviceId());
+        if (service == null) {
             return;
         }
 
-        log.info("Updates vSGs in {} with {}", vSgHost.id(), vSgs.toString());
-        vSgs.entrySet().stream()
-                .filter(entry -> hostService.getHostsByMac(entry.getValue()).isEmpty())
-                .forEach(entry -> addVirtualSubscriberGateway(
-                        vSgHost,
-                        entry.getKey(),
-                        entry.getValue(),
-                        serviceVlan));
-
-        hostService.getConnectedHosts(vSgHost.location()).stream()
-                .filter(host -> !host.mac().equals(vSgHost.mac()))
-                .filter(host -> !vSgs.values().contains(host.mac()))
-                .forEach(host -> {
-                    log.info("Removed vSG {}", host.toString());
-                    hostProvider.hostVanished(host.id());
-                });
-    }
-
-    /**
-     * Adds virtual subscriber gateway to the system.
-     *
-     * @param vSgHost host virtual machine of this vSG
-     * @param vSgIp vSG ip address
-     * @param vSgMac vSG mac address
-     * @param serviceVlan service vlan
-     */
-    // TODO address service specific task in a separate package
-    private void addVirtualSubscriberGateway(Host vSgHost, IpAddress vSgIp, MacAddress vSgMac,
-                                             String serviceVlan) {
-        log.info("vSG with IP({}) MAC({}) added", vSgIp.toString(), vSgMac.toString());
-
-        HostId hostId = HostId.hostId(vSgMac);
-        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
-                .set(S_TAG, serviceVlan)
-                .set(VSG_HOST_ID, vSgHost.id().toString())
-                .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
-
-        HostDescription hostDesc = new DefaultHostDescription(
-                vSgMac,
-                VlanId.NONE,
-                vSgHost.location(),
-                Sets.newHashSet(vSgIp),
-                annotations.build());
-
-        hostProvider.hostDetected(hostId, hostDesc, false);
-    }
-
-    /**
-     * Returns public ip addresses of vSGs running inside a give vSG host.
-     *
-     * @param vSgHost vSG host
-     * @return map of ip and mac address, or empty map
-     */
-    // TODO address service specific task in a separate package
-    private Map<IpAddress, MacAddress> getSubscriberGateways(Host vSgHost) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        String vtnPortId = vSgHost.annotations().value(PORT_ID);
-        String sTag = vSgHost.annotations().value(S_TAG);
-
-        if (Strings.isNullOrEmpty(vtnPortId) || Strings.isNullOrEmpty(sTag)) {
-            log.warn("PORT_ID and S_TAG is not set, ignore {}", vSgHost);
-            return Maps.newHashMap();
+        if (!service.providerServices().isEmpty()) {
+            removeInstanceFromTenantService(instance, service);
         }
-
-        // TODO remove openstack access when XOS provides all information
-        VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
-        VtnPort vtnPort = portApi.vtnPort(VtnPortId.of(vtnPortId), osAccess);
-        if (vtnPort == null) {
-            log.warn("Failed to get port information of {}", vSgHost);
-            return Maps.newHashMap();
-        }
-
-        if (!sTag.equals(getServiceVlan(vtnPort))) {
-            log.error("Host({}) s-tag does not match with VTN port s-tag", vSgHost);
-            return Maps.newHashMap();
-        }
-        return vtnPort.addressPairs();
-    }
-
-    /**
-     * Returns s-tag from a given VTN port.
-     *
-     * @param vtnPort vtn port
-     * @return s-tag string
-     */
-    // TODO address service specific task in a separate package
-    private String getServiceVlan(VtnPort vtnPort) {
-        checkNotNull(vtnPort);
-
-        String portName = vtnPort.name();
-        if (portName != null && portName.startsWith(S_TAG)) {
-            return portName.split("-")[1];
-        } else {
-            return null;
+        if (!service.tenantServices().isEmpty()) {
+            updateProviderServiceInstances(service);
         }
     }
 
-    /**
-     * Returns instances with a given network service.
-     *
-     * @param serviceId service id
-     * @return set of hosts
-     */
-    private Set<Host> getInstances(VtnServiceId serviceId) {
-        return StreamSupport.stream(hostService.getHosts().spliterator(), false)
-                .filter(host -> Objects.equals(
-                        serviceId.id(),
-                        host.annotations().value(SERVICE_ID)))
+    private void updateProviderServiceInstances(VtnService service) {
+        GroupKey groupKey = getGroupKey(service.id());
+
+        Set<DeviceId> devices = nodeManager.completeNodes().stream()
+                .map(CordVtnNode::intBrId)
                 .collect(Collectors.toSet());
-    }
 
-    /**
-     * Registers static DHCP lease for a given host.
-     *
-     * @param host host
-     * @param service cord service
-     */
-    private void registerDhcpLease(Host host, VtnService service) {
-        List<Ip4Address> options = Lists.newArrayList();
-        options.add(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()));
-        options.add(service.serviceIp().getIp4Address());
-        options.add(service.serviceIp().getIp4Address());
-        options.add(DEFAULT_DNS);
-
-        log.debug("Set static DHCP mapping for {}", host.mac());
-        dhcpService.setStaticMapping(host.mac(),
-                                     host.ipAddresses().stream().findFirst().get().getIp4Address(),
-                                     true,
-                                     options);
-    }
-
-    /**
-     * Handles VM detected situation.
-     *
-     * @param host host
-     */
-    private void serviceVmAdded(Host host) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        // TODO address service specific task in a separate package
-        String serviceVlan = host.annotations().value(S_TAG);
-        if (serviceVlan != null) {
-            virtualSubscriberGatewayAdded(host, serviceVlan);
-        }
-
-        String serviceId = host.annotations().value(SERVICE_ID);
-        if (Strings.isNullOrEmpty(serviceId)) {
-            // ignore this host, it is not a service instance
-            return;
-        }
-
-        log.info("Instance is detected {}", host);
-
-        // TODO remove openstack access when XOS provides all information
-        VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
-        VtnService service = serviceApi.service(VtnServiceId.of(serviceId), osAccess);
-        if (service == null) {
-            log.warn("Failed to get VtnService for {}", serviceId);
-            return;
-        }
-
-        switch (service.networkType()) {
-            case MANAGEMENT:
-                ruleInstaller.populateManagementNetworkRules(host, service);
-                break;
-            case PRIVATE:
-                arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
-            case PUBLIC:
-            default:
-                // TODO get bidirectional information from XOS once XOS supports
-                service.tenantServices().stream().forEach(
-                        tServiceId -> createServiceDependency(tServiceId, service.id(), true));
-                service.providerServices().stream().forEach(
-                        pServiceId -> createServiceDependency(service.id(), pServiceId, true));
-
-                ruleInstaller.updateProviderServiceGroup(service);
-                // sends gratuitous ARP here for the case of adding existing VMs
-                // when ONOS or cordvtn app is restarted
-                arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(host));
-                break;
-        }
-
-        registerDhcpLease(host, service);
-        ruleInstaller.populateBasicConnectionRules(host, service, true);
-    }
-
-    /**
-     * Handles VM removed situation.
-     *
-     * @param host host
-     */
-    private void serviceVmRemoved(Host host) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        // TODO address service specific task in a separate package
-        if (host.annotations().value(S_TAG) != null) {
-            virtualSubscriberGatewayRemoved(host);
-        }
-
-        String serviceId = host.annotations().value(SERVICE_ID);
-        if (Strings.isNullOrEmpty(serviceId)) {
-            // ignore this host, it is not a service instance
-            return;
-        }
-
-        log.info("Instance is vanished {}", host);
-
-        // TODO remove openstack access when XOS provides all information
-        VtnServiceApi vtnServiceApi = xosClient.getClient(xosAccess).vtnService();
-        VtnService service = vtnServiceApi.service(VtnServiceId.of(serviceId), osAccess);
-        if (service == null) {
-            log.warn("Failed to get VtnService for {}", serviceId);
-            return;
-        }
-
-        // TODO need to consider the case that the service is removed also
-        switch (service.networkType()) {
-            case MANAGEMENT:
-                break;
-            case PRIVATE:
-                if (getInstances(VtnServiceId.of(serviceId)).isEmpty()) {
-                    arpProxy.removeGateway(service.serviceIp());
-                }
-            case PUBLIC:
-            default:
-                if (!service.tenantServices().isEmpty()) {
-                    ruleInstaller.updateProviderServiceGroup(service);
-                }
-                if (!service.providerServices().isEmpty()) {
-                    ruleInstaller.updateTenantServiceVm(host, service);
-                }
-                break;
-        }
-
-        dhcpService.removeStaticMapping(host.mac());
-        ruleInstaller.populateBasicConnectionRules(host, service, false);
-    }
-
-
-    /**
-     * Handles virtual subscriber gateway VM or container.
-     *
-     * @param host new host with stag, it can be vsg VM or vsg
-     * @param serviceVlan service vlan
-     */
-    // TODO address service specific task in a separate package
-    private void virtualSubscriberGatewayAdded(Host host, String serviceVlan) {
-        Map<IpAddress, MacAddress> vSgs;
-        Host vSgHost;
-
-        String vSgHostId = host.annotations().value(VSG_HOST_ID);
-        if (vSgHostId == null) {
-            log.info("vSG VM detected {}", host.id());
-
-            vSgHost = host;
-            vSgs = getSubscriberGateways(vSgHost);
-            vSgs.entrySet().stream().forEach(entry -> addVirtualSubscriberGateway(
-                    vSgHost,
-                    entry.getKey(),
-                    entry.getValue(),
-                    serviceVlan));
-        } else {
-            vSgHost = hostService.getHost(HostId.hostId(vSgHostId));
-            if (vSgHost == null) {
-                return;
+        for (DeviceId deviceId : devices) {
+            Group group = groupService.getGroup(deviceId, groupKey);
+            if (group == null) {
+                log.trace("No group exists for service {} in {}", service.id(), deviceId);
+                continue;
             }
 
-            log.info("vSG detected {}", host.id());
-            vSgs = getSubscriberGateways(vSgHost);
-        }
+            List<GroupBucket> oldBuckets = group.buckets().buckets();
+            List<GroupBucket> newBuckets = getServiceGroupBuckets(
+                    deviceId, service.vni(), getInstances(service.id())).buckets();
 
-        ruleInstaller.populateSubscriberGatewayRules(vSgHost, vSgs.keySet());
-    }
-
-    /**
-     * Handles virtual subscriber gateway removed.
-     *
-     * @param vSg vsg host to remove
-     */
-    // TODO address service specific task in a separate package
-    private void virtualSubscriberGatewayRemoved(Host vSg) {
-        String vSgHostId = vSg.annotations().value(VSG_HOST_ID);
-        if (vSgHostId == null) {
-            return;
-        }
-
-        Host vSgHost = hostService.getHost(HostId.hostId(vSgHostId));
-        if (vSgHost == null) {
-            return;
-        }
-
-        log.info("vSG removed {}", vSg.id());
-        Map<IpAddress, MacAddress> vSgs = getSubscriberGateways(vSgHost);
-        ruleInstaller.populateSubscriberGatewayRules(vSgHost, vSgs.keySet());
-    }
-
-    /**
-     * Sets service network gateway MAC address and sends out gratuitous ARP to all
-     * VMs to update the gateway MAC address.
-     *
-     * @param newMac mac address to update
-     */
-    private void setPrivateGatewayMac(MacAddress newMac) {
-        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
-        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
-
-        if (newMac == null || newMac.equals(privateGatewayMac)) {
-            // no updates, do nothing
-            return;
-        }
-
-        privateGatewayMac = newMac;
-        log.debug("Set service gateway MAC address to {}", privateGatewayMac.toString());
-
-        VtnServiceApi vtnServiceApi = xosClient.getClient(xosAccess).vtnService();
-        vtnServiceApi.services().stream().forEach(serviceId -> {
-            VtnService service = vtnServiceApi.service(serviceId, osAccess);
-            if (service != null) {
-                arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
-                arpProxy.sendGratuitousArpForGateway(service.serviceIp(), getInstances(serviceId));
+            if (oldBuckets.equals(newBuckets)) {
+                continue;
             }
+
+            List<GroupBucket> bucketsToRemove = Lists.newArrayList(oldBuckets);
+            bucketsToRemove.removeAll(newBuckets);
+            if (!bucketsToRemove.isEmpty()) {
+                groupService.removeBucketsFromGroup(
+                        deviceId,
+                        groupKey,
+                        new GroupBuckets(bucketsToRemove),
+                        groupKey, appId);
+            }
+
+            List<GroupBucket> bucketsToAdd = Lists.newArrayList(newBuckets);
+            bucketsToAdd.removeAll(oldBuckets);
+            if (!bucketsToAdd.isEmpty()) {
+                groupService.addBucketsToGroup(
+                        deviceId,
+                        groupKey,
+                        new GroupBuckets(bucketsToAdd),
+                        groupKey, appId);
+            }
+        }
+    }
+
+    private void removeInstanceFromTenantService(Instance instance, VtnService service) {
+        service.providerServices().stream().forEach(pServiceId -> {
+            Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
+            Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
+
+            inPorts.put(instance.deviceId(), Sets.newHashSet(instance.portNumber()));
+            outGroups.put(instance.deviceId(), getGroupId(pServiceId, instance.deviceId()));
+
+            inServiceRule(inPorts, outGroups, false);
         });
     }
 
-    /**
-     * Sets public gateway MAC address.
-     *
-     * @param publicGateways gateway ip and mac address pairs
-     */
-    private void setPublicGatewayMac(Map<IpAddress, MacAddress> publicGateways) {
-        publicGateways.entrySet()
-                .stream()
-                .forEach(entry -> {
-                    arpProxy.addGateway(entry.getKey(), entry.getValue());
-                    log.debug("Added public gateway IP {}, MAC {}",
-                              entry.getKey().toString(), entry.getValue().toString());
-                });
-        // TODO notice gateway MAC change to VMs holds this gateway IP
+    private void serviceDependencyRules(VtnService tService, VtnService pService,
+                                       boolean isBidirectional, boolean install) {
+        Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
+        Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
+
+        nodeManager.completeNodes().stream().forEach(node -> {
+            DeviceId deviceId = node.intBrId();
+            GroupId groupId = createServiceGroup(deviceId, pService);
+            outGroups.put(deviceId, groupId);
+
+            Set<PortNumber> tServiceInstances = getInstances(tService.id())
+                    .stream()
+                    .filter(instance -> instance.deviceId().equals(deviceId))
+                    .map(Instance::portNumber)
+                    .collect(Collectors.toSet());
+            inPorts.put(deviceId, tServiceInstances);
+        });
+
+        Ip4Prefix srcRange = tService.subnet().getIp4Prefix();
+        Ip4Prefix dstRange = pService.subnet().getIp4Prefix();
+
+        indirectAccessRule(srcRange, pService.serviceIp().getIp4Address(), outGroups, install);
+        directAccessRule(srcRange, dstRange, install);
+        if (isBidirectional) {
+            directAccessRule(dstRange, srcRange, install);
+        }
+        inServiceRule(inPorts, outGroups, install);
     }
 
-    /**
-     * Updates configurations.
-     */
-    private void readConfiguration() {
-        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
-        if (config == null) {
-            log.debug("No configuration found");
-            return;
+    private void indirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
+                                    Map<DeviceId, GroupId> outGroups, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(srcRange)
+                .matchIPDst(serviceIp.toIpPrefix())
+                .build();
+
+        for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .group(outGroup.getValue())
+                    .build();
+
+            FlowRule flowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_HIGH)
+                    .forDevice(outGroup.getKey())
+                    .forTable(TABLE_ACCESS_TYPE)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRule);
+        }
+    }
+
+    private void directAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(srcRange)
+                .matchIPDst(dstRange)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_DST_IP)
+                .build();
+
+        nodeManager.completeNodes().stream().forEach(node -> {
+            DeviceId deviceId = node.intBrId();
+            FlowRule flowRuleDirect = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_DEFAULT)
+                    .forDevice(deviceId)
+                    .forTable(TABLE_ACCESS_TYPE)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRuleDirect);
+        });
+    }
+
+    private void inServiceRule(Map<DeviceId, Set<PortNumber>> inPorts,
+                               Map<DeviceId, GroupId> outGroups, boolean install) {
+        for (Map.Entry<DeviceId, Set<PortNumber>> entry : inPorts.entrySet()) {
+            Set<PortNumber> ports = entry.getValue();
+            DeviceId deviceId = entry.getKey();
+
+            GroupId groupId = outGroups.get(deviceId);
+            if (groupId == null) {
+                continue;
+            }
+
+            ports.stream().forEach(port -> {
+                TrafficSelector selector = DefaultTrafficSelector.builder()
+                        .matchInPort(port)
+                        .build();
+
+                TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                        .group(groupId)
+                        .build();
+
+                FlowRule flowRule = DefaultFlowRule.builder()
+                        .fromApp(appId)
+                        .withSelector(selector)
+                        .withTreatment(treatment)
+                        .withPriority(PRIORITY_DEFAULT)
+                        .forDevice(deviceId)
+                        .forTable(TABLE_IN_SERVICE)
+                        .makePermanent()
+                        .build();
+
+                pipeline.processFlowRule(install, flowRule);
+            });
+        }
+    }
+
+    private GroupId getGroupId(VtnServiceId serviceId, DeviceId deviceId) {
+        return new DefaultGroupId(Objects.hash(serviceId, deviceId));
+    }
+
+    private GroupKey getGroupKey(VtnServiceId serviceId) {
+        return new DefaultGroupKey(serviceId.id().getBytes());
+    }
+
+    private GroupId createServiceGroup(DeviceId deviceId, VtnService service) {
+        GroupKey groupKey = getGroupKey(service.id());
+        Group group = groupService.getGroup(deviceId, groupKey);
+        GroupId groupId = getGroupId(service.id(), deviceId);
+
+        if (group != null) {
+            log.debug("Group {} is already exist in {}", service.id(), deviceId);
+            return groupId;
         }
 
-        xosAccess = config.xosAccess();
-        osAccess = config.openstackAccess();
+        GroupBuckets buckets = getServiceGroupBuckets(
+                deviceId, service.vni(), getInstances(service.id()));
+        GroupDescription groupDescription = new DefaultGroupDescription(
+                deviceId,
+                GroupDescription.Type.SELECT,
+                buckets,
+                groupKey,
+                groupId.id(),
+                appId);
 
-        setPrivateGatewayMac(config.privateGatewayMac());
-        setPublicGatewayMac(config.publicGateways());
+        groupService.addGroup(groupDescription);
+        return groupId;
+    }
+
+    private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId,
+                                                Set<Instance> instances) {
+        List<GroupBucket> buckets = Lists.newArrayList();
+        instances.stream().forEach(instance -> {
+            Ip4Address tunnelIp = nodeManager.dpIp(instance.deviceId()).getIp4Address();
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+            if (deviceId.equals(instance.deviceId())) {
+                tBuilder.setEthDst(instance.mac())
+                        .setOutput(instance.portNumber());
+            } else {
+                ExtensionTreatment tunnelDst =
+                        pipeline.tunnelDstTreatment(deviceId, tunnelIp);
+                tBuilder.setEthDst(instance.mac())
+                        .extension(tunnelDst, deviceId)
+                        .setTunnelId(tunnelId)
+                        .setOutput(nodeManager.tunnelPort(instance.deviceId()));
+            }
+            buckets.add(createSelectGroupBucket(tBuilder.build()));
+        });
+        return new GroupBuckets(buckets);
     }
 
     private class InternalHostListener implements HostListener {
@@ -692,50 +393,14 @@
                 return;
             }
 
+            Instance instance = Instance.of(host);
             switch (event.type()) {
                 case HOST_UPDATED:
                 case HOST_ADDED:
-                    eventExecutor.execute(() -> serviceVmAdded(host));
+                    eventExecutor.execute(() -> instanceDetected(instance));
                     break;
                 case HOST_REMOVED:
-                    eventExecutor.execute(() -> serviceVmRemoved(host));
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    private class InternalPacketProcessor implements PacketProcessor {
-
-        @Override
-        public void process(PacketContext context) {
-            if (context.isHandled()) {
-                return;
-            }
-
-            Ethernet ethPacket = context.inPacket().parsed();
-            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
-                return;
-            }
-
-            arpProxy.processArpPacket(context, ethPacket);
-        }
-    }
-
-    private class InternalConfigListener implements NetworkConfigListener {
-
-        @Override
-        public void event(NetworkConfigEvent event) {
-            if (!event.configClass().equals(CordVtnConfig.class)) {
-                return;
-            }
-
-            switch (event.type()) {
-                case CONFIG_ADDED:
-                case CONFIG_UPDATED:
-                    log.info("Network configuration changed");
-                    eventExecutor.execute(CordVtn.this::readConfiguration);
+                    eventExecutor.execute(() -> instanceRemoved(instance));
                     break;
                 default:
                     break;
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java
index c0b1d61..8200fcc 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java
@@ -22,6 +22,7 @@
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.cordvtn.api.Instance;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.Host;
 import org.onosproject.net.flow.DefaultTrafficSelector;
@@ -166,9 +167,9 @@
      * Emits gratuitous ARP when a gateway mac address has been changed.
      *
      * @param gatewayIp gateway ip address to update MAC
-     * @param hosts set of hosts to send gratuitous ARP packet
+     * @param instances set of instances to send gratuitous ARP packet
      */
-    public void sendGratuitousArpForGateway(IpAddress gatewayIp, Set<Host> hosts) {
+    public void sendGratuitousArpForGateway(IpAddress gatewayIp, Set<Instance> instances) {
         MacAddress gatewayMac = gateways.get(gatewayIp.getIp4Address());
         if (gatewayMac == null) {
             log.debug("Gateway {} is not registered to ARP proxy", gatewayIp.toString());
@@ -176,13 +177,13 @@
         }
 
         Ethernet ethArp = buildGratuitousArp(gatewayIp.getIp4Address(), gatewayMac);
-        hosts.stream().forEach(host -> {
+        instances.stream().forEach(instance -> {
             TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                    .setOutput(host.location().port())
+                    .setOutput(instance.portNumber())
                     .build();
 
             packetService.emit(new DefaultOutboundPacket(
-                    host.location().deviceId(),
+                    instance.deviceId(),
                     treatment,
                     ByteBuffer.wrap(ethArp.serialize())));
         });
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceHandler.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceHandler.java
new file mode 100644
index 0000000..197f8df
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceHandler.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.xosclient.api.OpenStackAccess;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceApi;
+import org.onosproject.xosclient.api.VtnServiceId;
+import org.onosproject.xosclient.api.XosAccess;
+import org.onosproject.xosclient.api.XosClientService;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
+import static org.onosproject.xosclient.api.VtnService.NetworkType.MANAGEMENT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides default virtual network connectivity for service instances.
+ */
+@Component(immediate = true)
+public abstract class CordVtnInstanceHandler implements InstanceHandler {
+
+    protected final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry configRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected XosClientService xosClient;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnNodeManager nodeManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnPipeline pipeline;
+
+    protected static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
+    protected static final String XOS_ACCESS_ERROR = "XOS access is not configured";
+
+    protected XosAccess xosAccess = null;
+    protected OpenStackAccess osAccess = null;
+    protected ApplicationId appId;
+    protected VtnService.ServiceType serviceType;
+    protected ExecutorService eventExecutor;
+
+    protected HostListener hostListener = new InternalHostListener();
+    protected NetworkConfigListener configListener = new InternalConfigListener();
+
+    protected void activate() {
+        // sub class should set service type and event executor in its activate method
+        appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
+
+        hostService.addListener(hostListener);
+        configRegistry.addListener(configListener);
+
+        log.info("Started");
+    }
+
+    protected void deactivate() {
+        hostService.removeListener(hostListener);
+        configRegistry.removeListener(configListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Override
+    public void instanceDetected(Instance instance) {
+        log.info("Instance is detected {}", instance);
+
+        VtnService service = getVtnService(instance.serviceId());
+        if (service == null) {
+            log.warn("Failed to get VtnService for {}", instance);
+            return;
+        }
+
+        if (service.networkType().equals(MANAGEMENT)) {
+            managementNetworkRules(instance, service, true);
+        }
+
+        defaultConnectionRules(instance, service, true);
+    }
+
+    @Override
+    public void instanceRemoved(Instance instance) {
+        log.info("Instance is removed {}", instance);
+
+        VtnService service = getVtnService(instance.serviceId());
+        if (service == null) {
+            log.warn("Failed to get VtnService for {}", instance);
+            return;
+        }
+
+        if (service.networkType().equals(MANAGEMENT)) {
+            managementNetworkRules(instance, service, false);
+        }
+
+        // TODO check if any stale management network rules are
+        defaultConnectionRules(instance, service, false);
+    }
+
+    protected VtnService getVtnService(VtnServiceId serviceId) {
+        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+        // TODO remove openstack access when XOS provides all information
+        VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
+        VtnService service = serviceApi.service(serviceId, osAccess);
+        if (service == null) {
+            log.warn("Failed to get VtnService for {}", serviceId);
+        }
+        return service;
+    }
+
+    protected Set<Instance> getInstances(VtnServiceId serviceId) {
+        return StreamSupport.stream(hostService.getHosts().spliterator(), false)
+                .filter(host -> Objects.equals(
+                        serviceId.id(),
+                        host.annotations().value(Instance.SERVICE_ID)))
+                .map(Instance::of)
+                .collect(Collectors.toSet());
+    }
+
+    private void defaultConnectionRules(Instance instance, VtnService service, boolean install) {
+        long vni = service.vni();
+        Ip4Prefix serviceIpRange = service.subnet().getIp4Prefix();
+
+        inPortRule(instance, install);
+        dstIpRule(instance, vni, install);
+        tunnelInRule(instance, vni, install);
+
+        if (install) {
+            directAccessRule(serviceIpRange, serviceIpRange, true);
+            serviceIsolationRule(serviceIpRange, true);
+        } else if (getInstances(service.id()).isEmpty()) {
+            directAccessRule(serviceIpRange, serviceIpRange, false);
+            serviceIsolationRule(serviceIpRange, false);
+        }
+    }
+
+    private void managementNetworkRules(Instance instance, VtnService service, boolean install) {
+
+        managementPerInstanceRule(instance, install);
+        if (install) {
+            managementBaseRule(instance, service, true);
+        } else if (!hostService.getConnectedHosts(instance.deviceId()).stream()
+                .filter(host -> Instance.of(host).serviceId().equals(service.id()))
+                .findAny()
+                .isPresent()) {
+            managementBaseRule(instance, service, false);
+        }
+    }
+
+    private void managementBaseRule(Instance instance, VtnService service, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP)
+                .matchArpTpa(service.serviceIp().getIp4Address())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_MANAGEMENT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(PortNumber.LOCAL)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(service.subnet())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_DST_IP)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_MANAGEMENT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(service.serviceIp().toIpPrefix())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_MANAGEMENT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_ACCESS_TYPE)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void managementPerInstanceRule(Instance instance, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(PortNumber.LOCAL)
+                .matchEthType(Ethernet.TYPE_ARP)
+                .matchArpTpa(instance.ipAddress().getIp4Address())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(instance.portNumber())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_MANAGEMENT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void inPortRule(Instance instance, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(instance.portNumber())
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(instance.ipAddress().toIpPrefix())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_ACCESS_TYPE)
+                .build();
+
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(instance.portNumber())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_IN_SERVICE)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_LOW)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void dstIpRule(Instance instance, long vni, boolean install) {
+        Ip4Address tunnelIp = nodeManager.dpIp(instance.deviceId()).getIp4Address();
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(instance.ipAddress().toIpPrefix())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(instance.mac())
+                .setOutput(instance.portNumber())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_DST_IP)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        for (CordVtnNode node : nodeManager.completeNodes()) {
+            if (node.intBrId().equals(instance.deviceId())) {
+                continue;
+            }
+
+            ExtensionTreatment tunnelDst = pipeline.tunnelDstTreatment(node.intBrId(), tunnelIp);
+            if (tunnelDst == null) {
+                continue;
+            }
+
+            treatment = DefaultTrafficTreatment.builder()
+                    .setEthDst(instance.mac())
+                    .setTunnelId(vni)
+                    .extension(tunnelDst, node.intBrId())
+                    .setOutput(nodeManager.tunnelPort(node.intBrId()))
+                    .build();
+
+            flowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_DEFAULT)
+                    .forDevice(node.intBrId())
+                    .forTable(TABLE_DST_IP)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRule);
+        }
+    }
+
+    private void tunnelInRule(Instance instance, long vni, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchTunnelId(vni)
+                .matchEthDst(instance.mac())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(instance.portNumber())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(TABLE_TUNNEL_IN)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void directAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(srcRange)
+                .matchIPDst(dstRange)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_DST_IP)
+                .build();
+
+
+        nodeManager.completeNodes().stream().forEach(node -> {
+            FlowRule flowRuleDirect = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_DEFAULT)
+                    .forDevice(node.intBrId())
+                    .forTable(TABLE_ACCESS_TYPE)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRuleDirect);
+        });
+    }
+
+    private void serviceIsolationRule(Ip4Prefix dstRange, boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(dstRange)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .drop()
+                .build();
+
+        nodeManager.completeNodes().stream().forEach(node -> {
+            FlowRule flowRuleDirect = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_LOW)
+                    .forDevice(node.intBrId())
+                    .forTable(TABLE_ACCESS_TYPE)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRuleDirect);
+        });
+    }
+
+    protected void readConfiguration() {
+        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+        if (config == null) {
+            log.debug("No configuration found");
+            return;
+        }
+        osAccess = config.openstackAccess();
+        xosAccess = config.xosAccess();
+    }
+
+    public class InternalHostListener implements HostListener {
+
+        @Override
+        public void event(HostEvent event) {
+            Host host = event.subject();
+            if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+                // do not allow to proceed without mastership
+                return;
+            }
+
+            Instance instance = Instance.of(host);
+            if (!Objects.equals(instance.serviceType(), serviceType)) {
+                // not my service instance, do nothing
+                return;
+            }
+
+            switch (event.type()) {
+                case HOST_UPDATED:
+                case HOST_ADDED:
+                    eventExecutor.execute(() -> instanceDetected(instance));
+                    break;
+                case HOST_REMOVED:
+                    eventExecutor.execute(() -> instanceRemoved(instance));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    public class InternalConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (!event.configClass().equals(CordVtnConfig.class)) {
+                return;
+            }
+
+            switch (event.type()) {
+                case CONFIG_ADDED:
+                case CONFIG_UPDATED:
+                    readConfiguration();
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceManager.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceManager.java
new file mode 100644
index 0000000..4b85c27
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceManager.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcp.DhcpService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Port;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.xosclient.api.OpenStackAccess;
+import org.onosproject.xosclient.api.VtnPort;
+import org.onosproject.xosclient.api.VtnPortApi;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceApi;
+import org.onosproject.xosclient.api.VtnServiceId;
+import org.onosproject.xosclient.api.XosAccess;
+import org.onosproject.xosclient.api.XosClientService;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.api.Instance.*;
+import static org.onosproject.xosclient.api.VtnService.NetworkType.PRIVATE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Adds or removes instances to network services.
+ */
+@Component(immediate = true)
+@Service(value = CordVtnInstanceManager.class)
+public class CordVtnInstanceManager extends AbstractProvider implements HostProvider {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String XOS_ACCESS_ERROR = "XOS access is not configured";
+    private static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
+    private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry configRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostProviderRegistry hostProviderRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DhcpService dhcpService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected XosClientService xosClient;
+
+    private final ConfigFactory configFactory =
+            new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
+                @Override
+                public CordVtnConfig createConfig() {
+                    return new CordVtnConfig();
+                }
+            };
+
+    private final ExecutorService eventExecutor =
+            newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-instance", "event-handler"));
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final HostListener hostListener = new InternalHostListener();
+    private final NetworkConfigListener configListener = new InternalConfigListener();
+
+    private ApplicationId appId;
+    private HostProviderService hostProvider;
+    private CordVtnArpProxy arpProxy; // TODO make it a component service
+    private MacAddress privateGatewayMac = MacAddress.NONE;
+    private XosAccess xosAccess = null;
+    private OpenStackAccess osAccess = null;
+
+    /**
+     * Creates an cordvtn host location provider.
+     */
+    public CordVtnInstanceManager() {
+        super(new ProviderId("host", CordVtnService.CORDVTN_APP_ID));
+    }
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
+
+        arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        arpProxy.requestPacket();
+
+        hostService.addListener(hostListener);
+        hostProvider = hostProviderRegistry.register(this);
+
+        configRegistry.registerConfigFactory(configFactory);
+        configRegistry.addListener(configListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        hostProviderRegistry.unregister(this);
+        hostService.removeListener(hostListener);
+
+        packetService.removeProcessor(packetProcessor);
+
+        configRegistry.unregisterConfigFactory(configFactory);
+        configRegistry.removeListener(configListener);
+
+        eventExecutor.shutdown();
+        log.info("Stopped");
+    }
+
+    @Override
+    public void triggerProbe(Host host) {
+        /*
+         * Note: In CORD deployment, we assume that all hosts are configured.
+         * Therefore no probe is required.
+         */
+    }
+
+    /**
+     * Adds a service instance at a given connect point.
+     *
+     * @param connectPoint connect point of the instance
+     */
+    public void addInstance(ConnectPoint connectPoint) {
+        Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+        if (port == null) {
+            log.debug("No port found from {}", connectPoint);
+            return;
+        }
+
+        VtnPort vtnPort = getVtnPort(port.annotations().value("portName"));
+        if (vtnPort == null) {
+            return;
+        }
+
+        VtnService vtnService = getVtnService(vtnPort.serviceId());
+        if (vtnService == null) {
+            return;
+        }
+
+        // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
+        // existing instances.
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(SERVICE_TYPE, vtnService.serviceType().toString())
+                .set(SERVICE_ID, vtnPort.serviceId().id())
+                .set(PORT_ID, vtnPort.id().id())
+                .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                vtnPort.mac(),
+                VlanId.NONE,
+                new HostLocation(connectPoint, System.currentTimeMillis()),
+                Sets.newHashSet(vtnPort.ip()),
+                annotations.build());
+
+        HostId hostId = HostId.hostId(vtnPort.mac());
+        hostProvider.hostDetected(hostId, hostDesc, false);
+    }
+
+    /**
+     * Adds a service instance with given host ID and host description.
+     *
+     * @param hostId host id
+     * @param description host description
+     */
+    public void addInstance(HostId hostId, HostDescription description) {
+        hostProvider.hostDetected(hostId, description, false);
+    }
+
+    /**
+     * Removes a service instance from a given connect point.
+     *
+     * @param connectPoint connect point
+     */
+    public void removeInstance(ConnectPoint connectPoint) {
+        hostService.getConnectedHosts(connectPoint)
+                .stream()
+                .forEach(host -> hostProvider.hostVanished(host.id()));
+    }
+
+    /**
+     * Removes service instance with given host ID.
+     *
+     * @param hostId host id
+     */
+    public void removeInstance(HostId hostId) {
+        hostProvider.hostVanished(hostId);
+    }
+
+    private void instanceDetected(Instance instance) {
+        VtnService service = getVtnService(instance.serviceId());
+        if (service == null) {
+            return;
+        }
+
+        if (service.networkType().equals(PRIVATE)) {
+            arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
+            arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(instance));
+        }
+
+        if (!instance.isNestedInstance()) {
+            registerDhcpLease(instance, service);
+        }
+    }
+
+    private void instanceRemoved(Instance instance) {
+        VtnService service = getVtnService(instance.serviceId());
+        if (service == null) {
+            return;
+        }
+
+        if (service.networkType().equals(PRIVATE) && getInstances(service.id()).isEmpty()) {
+            arpProxy.removeGateway(service.serviceIp());
+        }
+
+        if (!instance.isNestedInstance()) {
+            dhcpService.removeStaticMapping(instance.mac());
+        }
+    }
+
+    private void registerDhcpLease(Instance instance, VtnService service) {
+        List<Ip4Address> options = Lists.newArrayList();
+        options.add(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()));
+        options.add(service.serviceIp().getIp4Address());
+        options.add(service.serviceIp().getIp4Address());
+        options.add(DEFAULT_DNS);
+
+        log.debug("Set static DHCP mapping for {} {}", instance.mac(), instance.ipAddress());
+        dhcpService.setStaticMapping(instance.mac(),
+                                     instance.ipAddress(),
+                                     true,
+                                     options);
+    }
+
+    private VtnService getVtnService(VtnServiceId serviceId) {
+        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+        // TODO remove openstack access when XOS provides all information
+        VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
+        VtnService service = serviceApi.service(serviceId, osAccess);
+        if (service == null) {
+            log.warn("Failed to get VtnService for {}", serviceId);
+        }
+        return service;
+    }
+
+    private VtnPort getVtnPort(String portName) {
+        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+        // TODO remove openstack access when XOS provides all information
+        VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
+        VtnPort vtnPort = portApi.vtnPort(portName, osAccess);
+        if (vtnPort == null) {
+            log.warn("Failed to get port information of {}", portName);
+        }
+        return vtnPort;
+    }
+
+    private Set<Instance> getInstances(VtnServiceId serviceId) {
+        return StreamSupport.stream(hostService.getHosts().spliterator(), false)
+                .filter(host -> Objects.equals(
+                        serviceId.id(),
+                        host.annotations().value(Instance.SERVICE_ID)))
+                .map(Instance::of)
+                .collect(Collectors.toSet());
+    }
+
+    private void readConfiguration() {
+        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+        if (config == null) {
+            log.debug("No configuration found");
+            return;
+        }
+
+        log.info("Load CORD-VTN configurations");
+
+        xosAccess = config.xosAccess();
+        osAccess = config.openstackAccess();
+        privateGatewayMac = config.privateGatewayMac();
+
+        Map<IpAddress, MacAddress> publicGateways = config.publicGateways();
+        publicGateways.entrySet()
+                .stream()
+                .forEach(entry -> {
+                    arpProxy.addGateway(entry.getKey(), entry.getValue());
+                    log.debug("Added public gateway IP {}, MAC {}",
+                              entry.getKey(), entry.getValue());
+                });
+        // TODO notice gateway MAC change to VMs holds this gateway IP
+    }
+
+    private class InternalHostListener implements HostListener {
+
+        @Override
+        public void event(HostEvent event) {
+            Host host = event.subject();
+            if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+                // do not allow to proceed without mastership
+                return;
+            }
+
+            Instance instance = Instance.of(host);
+            switch (event.type()) {
+                case HOST_UPDATED:
+                case HOST_ADDED:
+                    eventExecutor.execute(() -> instanceDetected(instance));
+                    break;
+                case HOST_REMOVED:
+                    eventExecutor.execute(() -> instanceRemoved(instance));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+            Ethernet ethPacket = context.inPacket().parsed();
+            if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+                return;
+            }
+            arpProxy.processArpPacket(context, ethPacket);
+        }
+    }
+
+    private class InternalConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (!event.configClass().equals(CordVtnConfig.class)) {
+                return;
+            }
+
+            switch (event.type()) {
+                case CONFIG_ADDED:
+                case CONFIG_UPDATED:
+                    readConfiguration();
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java
index a3e7bd4..7fa6c5c 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java
@@ -44,6 +44,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.BridgeConfig;
 import org.onosproject.net.behaviour.BridgeName;
 import org.onosproject.net.behaviour.ControllerInfo;
@@ -59,8 +60,6 @@
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.group.GroupService;
 import org.onosproject.net.host.HostService;
 import org.onosproject.ovsdb.controller.OvsdbClientService;
 import org.onosproject.ovsdb.controller.OvsdbController;
@@ -86,6 +85,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.DEFAULT_TUNNEL;
 import static org.onosproject.cordvtn.impl.RemoteIpCommandUtil.*;
 import static org.onosproject.net.Device.Type.SWITCH;
 import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
@@ -110,7 +110,6 @@
             .register(NetworkAddress.class);
 
     private static final String DEFAULT_BRIDGE = "br-int";
-    private static final String DEFAULT_TUNNEL = "vxlan";
     private static final String VPORT_PREFIX = "tap";
     private static final String OK = "OK";
     private static final String NO = "NO";
@@ -152,19 +151,16 @@
     protected HostService hostService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected FlowRuleService flowRuleService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected LeadershipService leadershipService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected GroupService groupService;
+    protected CordVtnInstanceManager instanceManager;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CordVtnService cordVtnService;
+    protected CordVtnPipeline pipeline;
 
     private final ExecutorService eventExecutor =
-            newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtncfg", "event-handler"));
+            newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-node", "event-handler"));
 
     private final NetworkConfigListener configListener = new InternalConfigListener();
     private final DeviceListener deviceListener = new InternalDeviceListener();
@@ -174,7 +170,6 @@
     private final BridgeHandler bridgeHandler = new BridgeHandler();
 
     private ConsistentMap<String, CordVtnNode> nodeStore;
-    private CordVtnRuleInstaller ruleInstaller;
     private ApplicationId appId;
     private NodeId localNodeId;
 
@@ -225,6 +220,7 @@
     @Activate
     protected void activate() {
         appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
+
         localNodeId = clusterService.getLocalNode().id();
         leadershipService.runForLeadership(appId.name());
 
@@ -234,13 +230,6 @@
                 .withApplicationId(appId)
                 .build();
 
-        ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
-                                                 deviceService,
-                                                 groupService,
-                                                 hostService,
-                                                 configRegistry,
-                                                 DEFAULT_TUNNEL);
-
         nodeStore.addListener(nodeStoreListener);
         deviceService.addListener(deviceListener);
         configService.addListener(configListener);
@@ -286,20 +275,6 @@
     }
 
     /**
-     * Initiates node to serve virtual tenant network.
-     *
-     * @param node cordvtn node
-     */
-    private void initNode(CordVtnNode node) {
-        checkNotNull(node);
-
-        NodeState state = (NodeState) node.state();
-        log.debug("Processing node: {} state: {}", node.hostname(), state);
-
-        state.process(this, node);
-    }
-
-    /**
      * Returns node initialization state.
      *
      * @param node cordvtn node
@@ -311,30 +286,6 @@
     }
 
     /**
-     * Flush flows installed by cordvtn.
-     */
-    public void flushRules() {
-        ruleInstaller.flushRules();
-    }
-
-    /**
-     * Returns if current node state saved in nodeStore is COMPLETE or not.
-     *
-     * @param node cordvtn node
-     * @return true if it's complete state, otherwise false
-     */
-    private boolean isNodeStateComplete(CordVtnNode node) {
-        checkNotNull(node);
-
-        // the state saved in nodeStore can be wrong if IP address settings are changed
-        // after the node init has been completed since there's no way to detect it
-        // getNodeState and checkNodeInitState always return correct answer but can be slow
-        Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
-        CordVtnNodeState state = versionedNode.value().state();
-        return state != null && state.equals(NodeState.COMPLETE);
-    }
-
-    /**
      * Returns detailed node initialization state.
      *
      * @param node cordvtn node
@@ -392,33 +343,122 @@
      * @return list of nodes
      */
     public List<CordVtnNode> getNodes() {
-        return nodeStore.values().stream()
-                .map(Versioned::value)
-                .collect(Collectors.toList());
+        return nodeStore.values().stream().map(Versioned::value).collect(Collectors.toList());
     }
 
     /**
-     * Returns cordvtn node associated with a given OVSDB device.
+     * Returns all nodes in complete state.
      *
-     * @param ovsdbId OVSDB device id
-     * @return cordvtn node, null if it fails to find the node
+     * @return set of nodes
      */
-    private CordVtnNode getNodeByOvsdbId(DeviceId ovsdbId) {
-        return getNodes().stream()
-                .filter(node -> node.ovsdbId().equals(ovsdbId))
-                .findFirst().orElse(null);
+    public Set<CordVtnNode> completeNodes() {
+        return getNodes().stream().filter(this::isNodeInitComplete).collect(Collectors.toSet());
     }
 
     /**
-     * Returns cordvtn node associated with a given integration bridge.
+     * Returns physical data plane port number of a given device.
      *
-     * @param bridgeId device id of integration bridge
-     * @return cordvtn node, null if it fails to find the node
+     * @param deviceId integration bridge device id
+     * @return port number; null otherwise
      */
-    private CordVtnNode getNodeByBridgeId(DeviceId bridgeId) {
-        return getNodes().stream()
-                .filter(node -> node.intBrId().equals(bridgeId))
+    public PortNumber dpPort(DeviceId deviceId) {
+        CordVtnNode node = nodeByBridgeId(deviceId);
+        if (node == null) {
+            log.warn("Failed to get node for {}", deviceId);
+            return null;
+        }
+        Port port = deviceService.getPorts(deviceId).stream()
+                .filter(p -> portName(p).contains(node.dpIntf()) &&
+                        p.isEnabled())
                 .findFirst().orElse(null);
+
+        return port == null ? null : port.number();
+    }
+
+    /**
+     * Returns physical data plane IP address of a given device.
+     *
+     * @param deviceId integration bridge device id
+     * @return ip address; null otherwise
+     */
+    public IpAddress dpIp(DeviceId deviceId) {
+        CordVtnNode node = nodeByBridgeId(deviceId);
+        if (node == null) {
+            log.warn("Failed to get node for {}", deviceId);
+            return null;
+        }
+        return node.dpIp().ip();
+    }
+
+    /**
+     * Returns tunnel port number of a given device.
+     *
+     * @param deviceId integration bridge device id
+     * @return port number
+     */
+    public PortNumber tunnelPort(DeviceId deviceId) {
+        Port port = deviceService.getPorts(deviceId).stream()
+                .filter(p -> portName(p).contains(DEFAULT_TUNNEL))
+                .findFirst().orElse(null);
+
+        return port == null ? null : port.number();
+    }
+
+    /**
+     * Returns if current node state saved in nodeStore is COMPLETE or not.
+     *
+     * @param node cordvtn node
+     * @return true if it's complete state, otherwise false
+     */
+    private boolean isNodeStateComplete(CordVtnNode node) {
+        checkNotNull(node);
+
+        // the state saved in nodeStore can be wrong if IP address settings are changed
+        // after the node init has been completed since there's no way to detect it
+        // getNodeState and checkNodeInitState always return correct answer but can be slow
+        Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
+        CordVtnNodeState state = versionedNode.value().state();
+        return state != null && state.equals(NodeState.COMPLETE);
+    }
+
+    /**
+     * Initiates node to serve virtual tenant network.
+     *
+     * @param node cordvtn node
+     */
+    private void initNode(CordVtnNode node) {
+        checkNotNull(node);
+
+        NodeState state = (NodeState) node.state();
+        log.debug("Processing node: {} state: {}", node.hostname(), state);
+
+        state.process(this, node);
+    }
+
+    /**
+     * Performs tasks after node initialization.
+     * It disconnects unnecessary OVSDB connection and installs initial flow
+     * rules on the device.
+     *
+     * @param node cordvtn node
+     */
+    private void postInit(CordVtnNode node) {
+        disconnectOvsdb(node);
+        pipeline.initPipeline(node, dpPort(node.intBrId()), tunnelPort(node.intBrId()));
+
+        deviceService.getPorts(node.intBrId()).stream()
+                .filter(port -> portName(port).startsWith(VPORT_PREFIX) &&
+                        port.isEnabled())
+                .forEach(port -> instanceManager.addInstance(connectPoint(port)));
+
+        hostService.getHosts().forEach(host -> {
+            if (deviceService.getPort(host.location().deviceId(),
+                                      host.location().port()) == null) {
+                instanceManager.removeInstance(connectPoint(host));
+            }
+        });
+
+        log.info("Finished init {}", node.hostname());
     }
 
     /**
@@ -456,45 +496,6 @@
     }
 
     /**
-     * Performs tasks after node initialization.
-     * It disconnects unnecessary OVSDB connection and installs initial flow
-     * rules on the device.
-     *
-     * @param node cordvtn node
-     */
-    private void postInit(CordVtnNode node) {
-        disconnectOvsdb(node);
-
-        ruleInstaller.init(node.intBrId(), node.dpIntf(), node.dpIp().ip());
-
-        // add existing hosts to the service
-        deviceService.getPorts(node.intBrId()).stream()
-                .filter(port -> getPortName(port).startsWith(VPORT_PREFIX) &&
-                        port.isEnabled())
-                .forEach(port -> cordVtnService.addServiceVm(node, getConnectPoint(port)));
-
-        // remove stale hosts from the service
-        hostService.getHosts().forEach(host -> {
-            Port port = deviceService.getPort(host.location().deviceId(), host.location().port());
-            if (port == null) {
-                cordVtnService.removeServiceVm(getConnectPoint(host));
-            }
-        });
-
-        log.info("Finished init {}", node.hostname());
-    }
-
-    /**
-     * Returns port name.
-     *
-     * @param port port
-     * @return port name
-     */
-    private String getPortName(Port port) {
-        return port.annotations().value("portName");
-    }
-
-    /**
      * Returns connection state of OVSDB server for a given node.
      *
      * @param node cordvtn node
@@ -587,7 +588,7 @@
                 BridgeConfig bridgeConfig =  device.as(BridgeConfig.class);
                 bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
             } else {
-                log.warn("The bridging behaviour is not supported in device {}", device.id().toString());
+                log.warn("The bridging behaviour is not supported in device {}", device.id());
             }
         } catch (ItemNotFoundException e) {
             log.warn("Failed to create integration bridge on {}", node.hostname());
@@ -619,7 +620,7 @@
                 TunnelConfig tunnelConfig =  device.as(TunnelConfig.class);
                 tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
             } else {
-                log.warn("The tunneling behaviour is not supported in device {}", device.id().toString());
+                log.warn("The tunneling behaviour is not supported in device {}", device.id());
             }
         } catch (ItemNotFoundException e) {
             log.warn("Failed to create tunnel interface on {}", node.hostname());
@@ -654,7 +655,7 @@
                 BridgeConfig bridgeConfig =  device.as(BridgeConfig.class);
                 bridgeConfig.addPort(BridgeName.bridgeName(DEFAULT_BRIDGE), node.dpIntf());
             } else {
-                log.warn("The bridging behaviour is not supported in device {}", device.id().toString());
+                log.warn("The bridging behaviour is not supported in device {}", device.id());
             }
         } catch (ItemNotFoundException e) {
             log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
@@ -712,7 +713,7 @@
     private boolean isTunnelIntfCreated(CordVtnNode node) {
         return deviceService.getPorts(node.intBrId())
                     .stream()
-                    .filter(p -> getPortName(p).contains(DEFAULT_TUNNEL) &&
+                    .filter(p -> portName(p).contains(DEFAULT_TUNNEL) &&
                             p.isEnabled())
                     .findAny().isPresent();
     }
@@ -726,7 +727,7 @@
     private boolean isDataPlaneIntfAdded(CordVtnNode node) {
         return deviceService.getPorts(node.intBrId())
                     .stream()
-                    .filter(p -> getPortName(p).contains(node.dpIntf()) &&
+                    .filter(p -> portName(p).contains(node.dpIntf()) &&
                             p.isEnabled())
                     .findAny().isPresent();
     }
@@ -761,7 +762,7 @@
      * @param port port
      * @return connect point
      */
-    private ConnectPoint getConnectPoint(Port port) {
+    private ConnectPoint connectPoint(Port port) {
         return new ConnectPoint(port.element().id(), port.number());
     }
 
@@ -771,15 +772,49 @@
      * @param host host
      * @return connect point
      */
-    private ConnectPoint getConnectPoint(Host host) {
+    private ConnectPoint connectPoint(Host host) {
         return new ConnectPoint(host.location().deviceId(), host.location().port());
     }
 
+    /**
+     * Returns cordvtn node associated with a given OVSDB device.
+     *
+     * @param ovsdbId OVSDB device id
+     * @return cordvtn node, null if it fails to find the node
+     */
+    private CordVtnNode nodeByOvsdbId(DeviceId ovsdbId) {
+        return getNodes().stream()
+                .filter(node -> node.ovsdbId().equals(ovsdbId))
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Returns cordvtn node associated with a given integration bridge.
+     *
+     * @param bridgeId device id of integration bridge
+     * @return cordvtn node, null if it fails to find the node
+     */
+    private CordVtnNode nodeByBridgeId(DeviceId bridgeId) {
+        return getNodes().stream()
+                .filter(node -> node.intBrId().equals(bridgeId))
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Returns port name.
+     *
+     * @param port port
+     * @return port name
+     */
+    private String portName(Port port) {
+        return port.annotations().value("portName");
+    }
+
     private class OvsdbHandler implements ConnectionHandler<Device> {
 
         @Override
         public void connected(Device device) {
-            CordVtnNode node = getNodeByOvsdbId(device.id());
+            CordVtnNode node = nodeByOvsdbId(device.id());
             if (node != null) {
                 setNodeState(node, getNodeState(node));
             } else {
@@ -800,7 +835,7 @@
 
         @Override
         public void connected(Device device) {
-            CordVtnNode node = getNodeByBridgeId(device.id());
+            CordVtnNode node = nodeByBridgeId(device.id());
             if (node != null) {
                 setNodeState(node, getNodeState(node));
             } else {
@@ -810,7 +845,7 @@
 
         @Override
         public void disconnected(Device device) {
-            CordVtnNode node = getNodeByBridgeId(device.id());
+            CordVtnNode node = nodeByBridgeId(device.id());
             if (node != null) {
                 log.debug("Integration Bridge is disconnected from {}", node.hostname());
                 setNodeState(node, NodeState.INCOMPLETE);
@@ -825,8 +860,8 @@
          * @param port port
          */
         public void portAdded(Port port) {
-            CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
-            String portName = getPortName(port);
+            CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
+            String portName = portName(port);
 
             if (node == null) {
                 log.debug("{} is added to unregistered node, ignore it.", portName);
@@ -837,7 +872,7 @@
 
             if (portName.startsWith(VPORT_PREFIX)) {
                 if (isNodeStateComplete(node)) {
-                    cordVtnService.addServiceVm(node, getConnectPoint(port));
+                    instanceManager.addInstance(connectPoint(port));
                 } else {
                     log.debug("VM is detected on incomplete node, ignore it.", portName);
                 }
@@ -854,8 +889,8 @@
          * @param port port
          */
         public void portRemoved(Port port) {
-            CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
-            String portName = getPortName(port);
+            CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
+            String portName = portName(port);
 
             if (node == null) {
                 return;
@@ -865,7 +900,7 @@
 
             if (portName.startsWith(VPORT_PREFIX)) {
                 if (isNodeStateComplete(node)) {
-                    cordVtnService.removeServiceVm(getConnectPoint(port));
+                    instanceManager.removeInstance(connectPoint(port));
                 } else {
                     log.debug("VM is vanished from incomplete node, ignore it.", portName);
                 }
@@ -922,7 +957,6 @@
             log.debug("No configuration found");
             return;
         }
-
         config.cordVtnNodes().forEach(this::addOrUpdateNode);
     }
 
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnPipeline.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnPipeline.java
new file mode 100644
index 0000000..4abbcf0
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnPipeline.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+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.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides CORD VTN pipeline.
+ */
+@Component(immediate = true)
+@Service(value = CordVtnPipeline.class)
+public final class CordVtnPipeline {
+
+    protected final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    // tables
+    public static final int TABLE_ZERO = 0;
+    public static final int TABLE_IN_PORT = 1;
+    public static final int TABLE_ACCESS_TYPE = 2;
+    public static final int TABLE_IN_SERVICE = 3;
+    public static final int TABLE_DST_IP = 4;
+    public static final int TABLE_TUNNEL_IN = 5;
+    public static final int TABLE_VLAN = 6;
+
+    // priorities
+    public static final int PRIORITY_MANAGEMENT = 55000;
+    public static final int PRIORITY_HIGH = 50000;
+    public static final int PRIORITY_DEFAULT = 5000;
+    public static final int PRIORITY_LOW = 4000;
+    public static final int PRIORITY_ZERO = 0;
+
+    public static final int VXLAN_UDP_PORT = 4789;
+    public static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
+    public static final String DEFAULT_TUNNEL = "vxlan";
+    private static final String PORT_NAME = "portName";
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        log.info("Stopped");
+    }
+
+    /**
+     * Flush flows installed by this application.
+     */
+    public void flushRules() {
+        flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
+    }
+
+    /**
+     * Installs table miss rule to a give device.
+     *
+     * @param node cordvtn node
+     * @param dpPort data plane port number
+     * @param tunnelPort tunnel port number
+     */
+    public void initPipeline(CordVtnNode node, PortNumber dpPort, PortNumber tunnelPort) {
+        checkNotNull(node);
+
+        processTableZero(node.intBrId(), dpPort, node.dpIp().ip());
+        processInPortTable(node.intBrId(), tunnelPort, dpPort);
+        processAccessTypeTable(node.intBrId(), dpPort);
+        processVlanTable(node.intBrId(), dpPort);
+    }
+
+    private void processTableZero(DeviceId deviceId, PortNumber dpPort, IpAddress dpIp) {
+        // take vxlan packet out onto the physical port
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(PortNumber.LOCAL)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dpPort)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_HIGH)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take a vxlan encap'd packet through the Linux stack
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(dpPort)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_HIGH)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take a packet to the data plane ip through Linux stack
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(dpPort)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(dpIp.toIpPrefix())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_HIGH)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take an arp packet from physical through Linux stack
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(dpPort)
+                .matchEthType(Ethernet.TYPE_ARP)
+                .matchArpTpa(dpIp.getIp4Address())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_HIGH)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take all else to the next table
+        selector = DefaultTrafficSelector.builder()
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_IN_PORT)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_ZERO)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take all vlan tagged packet to the VLAN table
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VlanId.ANY)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_VLAN)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_MANAGEMENT)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+    }
+
+    private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dpPort) {
+        checkNotNull(tunnelPort);
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(tunnelPort)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_TUNNEL_IN)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(deviceId)
+                .forTable(TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(dpPort)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_DST_IP)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(deviceId)
+                .forTable(TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+    }
+
+    private void processAccessTypeTable(DeviceId deviceId, PortNumber dpPort) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dpPort)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_ZERO)
+                .forDevice(deviceId)
+                .forTable(TABLE_ACCESS_TYPE)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+    }
+
+    private void processVlanTable(DeviceId deviceId, PortNumber dpPort) {
+        // for traffic going out to WAN, strip vid 500 and take through data plane interface
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_WAN)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .setOutput(dpPort)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(deviceId)
+                .forTable(TABLE_VLAN)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_WAN)
+                .matchEthType(Ethernet.TYPE_ARP)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_HIGH)
+                .forDevice(deviceId)
+                .forTable(TABLE_VLAN)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+    }
+
+    public void processFlowRule(boolean install, FlowRule rule) {
+        FlowRuleOperations.Builder oBuilder = FlowRuleOperations.builder();
+        oBuilder = install ? oBuilder.add(rule) : oBuilder.remove(rule);
+
+        flowRuleService.apply(oBuilder.build(new FlowRuleOperationsContext() {
+            @Override
+            public void onError(FlowRuleOperations ops) {
+                log.error(String.format("Failed %s, %s", ops.toString(), rule.toString()));
+            }
+        }));
+    }
+
+    public ExtensionTreatment tunnelDstTreatment(DeviceId deviceId, Ip4Address remoteIp) {
+        try {
+            Device device = deviceService.getDevice(deviceId);
+
+            if (device.is(ExtensionTreatmentResolver.class)) {
+                ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+                ExtensionTreatment treatment =
+                        resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+                treatment.setPropertyValue("tunnelDst", remoteIp);
+                return treatment;
+            } else {
+                log.warn("The extension treatment resolving behaviour is not supported in device {}",
+                         device.id().toString());
+                return null;
+            }
+        } catch (ItemNotFoundException | UnsupportedOperationException |
+                ExtensionPropertyException e) {
+            log.error("Failed to get extension instruction {}", deviceId);
+            return null;
+        }
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnRuleInstaller.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnRuleInstaller.java
deleted file mode 100644
index e2be9a6..0000000
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/CordVtnRuleInstaller.java
+++ /dev/null
@@ -1,1387 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.cordvtn.impl;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import org.onlab.packet.Ethernet;
-import org.onlab.packet.IPv4;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.Ip4Prefix;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.TpPort;
-import org.onlab.packet.VlanId;
-import org.onlab.util.ItemNotFoundException;
-import org.onosproject.cordvtn.api.CordVtnConfig;
-import org.onosproject.cordvtn.api.CordVtnNode;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.DefaultGroupId;
-import org.onosproject.core.GroupId;
-import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.Host;
-import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
-import org.onosproject.net.config.NetworkConfigRegistry;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleOperations;
-import org.onosproject.net.flow.FlowRuleOperationsContext;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flow.criteria.Criterion;
-import org.onosproject.net.flow.criteria.IPCriterion;
-import org.onosproject.net.flow.instructions.ExtensionPropertyException;
-import org.onosproject.net.flow.instructions.ExtensionTreatment;
-import org.onosproject.net.flow.instructions.Instruction;
-import org.onosproject.net.flow.instructions.Instructions;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction;
-import org.onosproject.net.group.DefaultGroupBucket;
-import org.onosproject.net.group.DefaultGroupDescription;
-import org.onosproject.net.group.DefaultGroupKey;
-import org.onosproject.net.group.Group;
-import org.onosproject.net.group.GroupBucket;
-import org.onosproject.net.group.GroupBuckets;
-import org.onosproject.net.group.GroupDescription;
-import org.onosproject.net.group.GroupKey;
-import org.onosproject.net.group.GroupService;
-import org.onosproject.net.host.HostService;
-import org.onosproject.xosclient.api.VtnService;
-import org.onosproject.xosclient.api.VtnServiceId;
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
-import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
-import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
-import static org.slf4j.LoggerFactory.getLogger;
-
-/**
- * Populates rules for CORD VTN service.
- */
-public class CordVtnRuleInstaller {
-
-    protected final Logger log = getLogger(getClass());
-
-    private static final int TABLE_FIRST = 0;
-    private static final int TABLE_IN_PORT = 1;
-    private static final int TABLE_ACCESS_TYPE = 2;
-    private static final int TABLE_IN_SERVICE = 3;
-    private static final int TABLE_DST_IP = 4;
-    private static final int TABLE_TUNNEL_IN = 5;
-    private static final int TABLE_Q_IN_Q = 6;
-
-    private static final int MANAGEMENT_PRIORITY = 55000;
-    private static final int VSG_PRIORITY = 55000;
-    private static final int HIGH_PRIORITY = 50000;
-    private static final int DEFAULT_PRIORITY = 5000;
-    private static final int LOW_PRIORITY = 4000;
-    private static final int LOWEST_PRIORITY = 0;
-
-    private static final int VXLAN_UDP_PORT = 4789;
-    private static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
-
-    private static final String PORT_NAME = "portName";
-    private static final String DATA_PLANE_INTF = "dataPlaneIntf";
-    private static final String DATA_PLANE_IP = "dataPlaneIp";
-    private static final String S_TAG = "stag";
-    private static final String SERVICE_ID = "serviceId";
-
-    private final ApplicationId appId;
-    private final FlowRuleService flowRuleService;
-    private final DeviceService deviceService;
-    private final GroupService groupService;
-    private final HostService hostService;
-    private final NetworkConfigRegistry configRegistry;
-    private final String tunnelType;
-
-    /**
-     * Creates a new rule populator.
-     *
-     * @param appId application id
-     * @param flowRuleService flow rule service
-     * @param deviceService device service
-     * @param groupService group service
-     * @param configRegistry config registry
-     * @param tunnelType tunnel type
-     */
-    public CordVtnRuleInstaller(ApplicationId appId,
-                                FlowRuleService flowRuleService,
-                                DeviceService deviceService,
-                                GroupService groupService,
-                                HostService hostService,
-                                NetworkConfigRegistry configRegistry,
-                                String tunnelType) {
-        this.appId = appId;
-        this.flowRuleService = flowRuleService;
-        this.deviceService = deviceService;
-        this.groupService = groupService;
-        this.hostService = hostService;
-        this.configRegistry = configRegistry;
-        this.tunnelType = checkNotNull(tunnelType);
-    }
-
-    /**
-     * Installs table miss rule to a give device.
-     *
-     * @param deviceId device id to install the rules
-     * @param dpIntf data plane interface name
-     * @param dpIp data plane ip address
-     */
-    public void init(DeviceId deviceId, String dpIntf, IpAddress dpIp) {
-        // default is drop packets which can be accomplished without
-        // a table miss entry for all table.
-        PortNumber tunnelPort = getTunnelPort(deviceId);
-        PortNumber dpPort = getDpPort(deviceId, dpIntf);
-
-        processFirstTable(deviceId, dpPort, dpIp);
-        processInPortTable(deviceId, tunnelPort, dpPort);
-        processAccessTypeTable(deviceId, dpPort);
-        processQInQTable(deviceId, dpPort);
-    }
-
-    /**
-     * Flush flows installed by this application.
-     */
-    public void flushRules() {
-        flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
-    }
-
-    /**
-     * Populates basic rules that connect a VM to the other VMs in the system.
-     *
-     * @param host host
-     * @param service cord service
-     * @param install true to install or false to remove
-     */
-    public void populateBasicConnectionRules(Host host, VtnService service, boolean install) {
-        checkNotNull(host);
-        checkNotNull(service);
-
-        DeviceId deviceId = host.location().deviceId();
-        PortNumber inPort = host.location().port();
-        MacAddress dstMac = host.mac();
-        IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
-
-        long tunnelId = service.vni();
-        Ip4Prefix serviceIpRange = service.subnet().getIp4Prefix();
-
-        populateLocalInPortRule(deviceId, inPort, hostIp, install);
-        populateDstIpRule(deviceId, inPort, dstMac, hostIp, tunnelId, getTunnelIp(host), install);
-        populateTunnelInRule(deviceId, inPort, dstMac, tunnelId, install);
-
-        if (install) {
-            populateDirectAccessRule(serviceIpRange, serviceIpRange, true);
-            populateServiceIsolationRule(serviceIpRange, true);
-        } else if (getInstances(service.id()).isEmpty()) {
-            // removes network related rules only if there's no hosts left in this network
-            populateDirectAccessRule(serviceIpRange, serviceIpRange, false);
-            populateServiceIsolationRule(serviceIpRange, false);
-        }
-    }
-
-    /**
-     * Creates provider service group and populates service dependency rules.
-     *
-     * @param tService tenant cord service
-     * @param pService provider cord service
-     * @param isBidirectional true to enable bidirectional connection between two services
-     * @param install true to install or false to remove
-     */
-    public void populateServiceDependencyRules(VtnService tService, VtnService pService,
-                                               boolean isBidirectional, boolean install) {
-        checkNotNull(tService);
-        checkNotNull(pService);
-
-        Ip4Prefix srcRange = tService.subnet().getIp4Prefix();
-        Ip4Prefix dstRange = pService.subnet().getIp4Prefix();
-        Ip4Address serviceIp = pService.serviceIp().getIp4Address();
-
-        Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
-        Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
-
-        getVirtualSwitches().stream().forEach(deviceId -> {
-            GroupId groupId = createServiceGroup(deviceId, pService);
-            outGroups.put(deviceId, groupId);
-
-            Set<PortNumber> tServiceVms = getInstances(tService.id())
-                    .stream()
-                    .filter(host -> host.location().deviceId().equals(deviceId))
-                    .map(host -> host.location().port())
-                    .collect(Collectors.toSet());
-            inPorts.put(deviceId, tServiceVms);
-        });
-
-        populateIndirectAccessRule(srcRange, serviceIp, outGroups, install);
-        populateDirectAccessRule(srcRange, dstRange, install);
-        if (isBidirectional) {
-            populateDirectAccessRule(dstRange, srcRange, install);
-        }
-        populateInServiceRule(inPorts, outGroups, install);
-    }
-
-    /**
-     * Updates group buckets for a given service to all devices.
-     *
-     * @param service cord service
-     */
-    public void updateProviderServiceGroup(VtnService service) {
-        checkNotNull(service);
-
-        GroupKey groupKey = getGroupKey(service.id());
-
-        for (DeviceId deviceId : getVirtualSwitches()) {
-            Group group = groupService.getGroup(deviceId, groupKey);
-            if (group == null) {
-                log.trace("No group exists for service {} in {}, do nothing.", service.id(), deviceId);
-                continue;
-            }
-
-            List<GroupBucket> oldBuckets = group.buckets().buckets();
-            List<GroupBucket> newBuckets = getServiceGroupBuckets(
-                    deviceId, service.vni(), getInstances(service.id())).buckets();
-
-            if (oldBuckets.equals(newBuckets)) {
-                continue;
-            }
-
-            List<GroupBucket> bucketsToRemove = new ArrayList<>(oldBuckets);
-            bucketsToRemove.removeAll(newBuckets);
-            if (!bucketsToRemove.isEmpty()) {
-                groupService.removeBucketsFromGroup(
-                        deviceId,
-                        groupKey,
-                        new GroupBuckets(bucketsToRemove),
-                        groupKey, appId);
-            }
-
-            List<GroupBucket> bucketsToAdd = new ArrayList<>(newBuckets);
-            bucketsToAdd.removeAll(oldBuckets);
-            if (!bucketsToAdd.isEmpty()) {
-                groupService.addBucketsToGroup(
-                        deviceId,
-                        groupKey,
-                        new GroupBuckets(bucketsToAdd),
-                        groupKey, appId);
-            }
-        }
-    }
-
-    /**
-     * Updates tenant service indirect access rules when VM is created or removed.
-     *
-     * @param host removed vm
-     * @param service tenant service
-     */
-    public void updateTenantServiceVm(Host host, VtnService service) {
-        checkNotNull(host);
-        checkNotNull(service);
-
-        DeviceId deviceId = host.location().deviceId();
-        PortNumber inPort = host.location().port();
-
-        service.providerServices().stream().forEach(pServiceId -> {
-            Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
-            Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
-
-            inPorts.put(deviceId, Sets.newHashSet(inPort));
-            outGroups.put(deviceId, getGroupId(pServiceId, deviceId));
-
-            populateInServiceRule(inPorts, outGroups, false);
-        });
-    }
-
-    /**
-     * Populates flow rules for management network access.
-     *
-     * @param host host which has management network interface
-     * @param mService management network service
-     */
-    public void populateManagementNetworkRules(Host host, VtnService mService) {
-        checkNotNull(mService);
-
-        DeviceId deviceId = host.location().deviceId();
-        IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
-
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_ARP)
-                .matchArpTpa(mService.serviceIp().getIp4Address())
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(MANAGEMENT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(PortNumber.LOCAL)
-                .matchEthType(Ethernet.TYPE_ARP)
-                .matchArpTpa(hostIp.getIp4Address())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(host.location().port())
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(MANAGEMENT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(PortNumber.LOCAL)
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(mService.subnet())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_DST_IP)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(MANAGEMENT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(mService.serviceIp().toIpPrefix())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(MANAGEMENT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_ACCESS_TYPE)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-    }
-
-    /**
-     * Populates rules for vSG VM.
-     *
-     * @param vSgHost vSG host
-     * @param vSgIps set of ip addresses of vSGs running inside the vSG VM
-     */
-    public void populateSubscriberGatewayRules(Host vSgHost, Set<IpAddress> vSgIps) {
-        VlanId serviceVlan = getServiceVlan(vSgHost);
-        PortNumber dpPort = getDpPort(vSgHost);
-
-        if (serviceVlan == null || dpPort == null) {
-            log.warn("Failed to populate rules for vSG VM {}", vSgHost.id());
-            return;
-        }
-
-        // for traffics with s-tag, strip the tag and take through the vSG VM
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchInPort(dpPort)
-                .matchVlanId(serviceVlan)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(vSgHost.location().port())
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(vSgHost.location().deviceId())
-                .forTable(TABLE_Q_IN_Q)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // for traffics with customer vlan, tag with the service vlan based on input port with
-        // lower priority to avoid conflict with WAN tag
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(vSgHost.location().port())
-                .matchVlanId(serviceVlan)
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(dpPort)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(vSgHost.location().deviceId())
-                .forTable(TABLE_Q_IN_Q)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // for traffic coming from WAN, tag 500 and take through the vSG VM
-        // based on destination ip
-        vSgIps.stream().forEach(ip -> {
-            TrafficSelector downstream = DefaultTrafficSelector.builder()
-                    .matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPDst(ip.toIpPrefix())
-                    .build();
-
-            TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
-                    .pushVlan()
-                    .setVlanId(VLAN_WAN)
-                    .setEthDst(vSgHost.mac())
-                    .setOutput(vSgHost.location().port())
-                    .build();
-
-            FlowRule downstreamFlowRule = DefaultFlowRule.builder()
-                    .fromApp(appId)
-                    .withSelector(downstream)
-                    .withTreatment(downstreamTreatment)
-                    .withPriority(DEFAULT_PRIORITY)
-                    .forDevice(vSgHost.location().deviceId())
-                    .forTable(TABLE_DST_IP)
-                    .makePermanent()
-                    .build();
-
-            processFlowRule(true, downstreamFlowRule);
-        });
-
-        // remove downstream flow rules for the vSG not shown in vSgIps
-        for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
-            if (!rule.deviceId().equals(vSgHost.location().deviceId())) {
-                continue;
-            }
-            PortNumber output = getOutputFromTreatment(rule);
-            if (output == null || !output.equals(vSgHost.location().port()) ||
-                    !isVlanPushFromTreatment(rule)) {
-                continue;
-            }
-
-            IpPrefix dstIp = getDstIpFromSelector(rule);
-            if (dstIp != null && !vSgIps.contains(dstIp.address())) {
-                processFlowRule(false, rule);
-            }
-        }
-    }
-
-    /**
-     * Populates default rules on the first table.
-     * It includes the rules for shuttling vxlan-encapped packets between ovs and
-     * linux stack,and external network connectivity.
-     *
-     * @param deviceId device id
-     * @param dpPort data plane interface port number
-     * @param dpIp data plane ip address
-     */
-    private void processFirstTable(DeviceId deviceId, PortNumber dpPort, IpAddress dpIp) {
-        // take vxlan packet out onto the physical port
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchInPort(PortNumber.LOCAL)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(dpPort)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(HIGH_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // take a vxlan encap'd packet through the Linux stack
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(dpPort)
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(HIGH_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // take a packet to the data plane ip through Linux stack
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(dpPort)
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(dpIp.toIpPrefix())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(HIGH_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // take an arp packet from physical through Linux stack
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(dpPort)
-                .matchEthType(Ethernet.TYPE_ARP)
-                .matchArpTpa(dpIp.getIp4Address())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(HIGH_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // take all else to the next table
-        selector = DefaultTrafficSelector.builder()
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_IN_PORT)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(LOWEST_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        // take all vlan tagged packet to the Q_IN_Q table
-        selector = DefaultTrafficSelector.builder()
-                .matchVlanId(VlanId.ANY)
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_Q_IN_Q)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(VSG_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_FIRST)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-    }
-
-    /**
-     * Forward table miss packets in ACCESS_TYPE table to data plane port.
-     *
-     * @param deviceId device id
-     * @param dpPort data plane interface port number
-     */
-    private void processAccessTypeTable(DeviceId deviceId, PortNumber dpPort) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(dpPort)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(LOWEST_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_ACCESS_TYPE)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-    }
-
-    /**
-     * Populates default rules for IN_PORT table.
-     * All packets from tunnel port are forwarded to TUNNEL_ID table and all packets
-     * from data plane interface port to ACCESS_TYPE table.
-     *
-     * @param deviceId device id to install the rules
-     * @param tunnelPort tunnel port number
-     * @param dpPort data plane interface port number
-     */
-    private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dpPort) {
-        checkNotNull(tunnelPort);
-
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchInPort(tunnelPort)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_TUNNEL_IN)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_IN_PORT)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(dpPort)
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_DST_IP)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_IN_PORT)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-    }
-
-    /**
-     * Populates default rules for Q_IN_Q table.
-     *
-     * @param deviceId device id
-     * @param dpPort data plane interface port number
-     */
-    private void processQInQTable(DeviceId deviceId, PortNumber dpPort) {
-        // for traffic going out to WAN, strip vid 500 and take through data plane interface
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchVlanId(VLAN_WAN)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .popVlan()
-                .setOutput(dpPort)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_Q_IN_Q)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchVlanId(VLAN_WAN)
-                .matchEthType(Ethernet.TYPE_ARP)
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.CONTROLLER)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(HIGH_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_Q_IN_Q)
-                .makePermanent()
-                .build();
-
-        processFlowRule(true, flowRule);
-    }
-
-    /**
-     * Populates rules for local in port in IN_PORT table.
-     * Flows from a given in port, whose source IP is service IP transition
-     * to DST_TYPE table. Other flows transition to IN_SERVICE table.
-     *
-     * @param deviceId device id to install the rules
-     * @param inPort in port
-     * @param srcIp source ip
-     * @param install true to install or false to remove
-     */
-    private void populateLocalInPortRule(DeviceId deviceId, PortNumber inPort, IpAddress srcIp,
-                                         boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchInPort(inPort)
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPSrc(srcIp.toIpPrefix())
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_ACCESS_TYPE)
-                .build();
-
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_IN_PORT)
-                .makePermanent()
-                .build();
-
-        processFlowRule(install, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(inPort)
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_IN_SERVICE)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(LOW_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_IN_PORT)
-                .makePermanent()
-                .build();
-
-        processFlowRule(install, flowRule);
-    }
-
-    /**
-     * Populates direct VM access rules for ACCESS_TYPE table.
-     * These rules are installed to all devices.
-     *
-     * @param srcRange source ip range
-     * @param dstRange destination ip range
-     * @param install true to install or false to remove
-     */
-    private void populateDirectAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPSrc(srcRange)
-                .matchIPDst(dstRange)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_DST_IP)
-                .build();
-
-
-        getVirtualSwitches().stream().forEach(deviceId -> {
-            FlowRule flowRuleDirect = DefaultFlowRule.builder()
-                    .fromApp(appId)
-                    .withSelector(selector)
-                    .withTreatment(treatment)
-                    .withPriority(DEFAULT_PRIORITY)
-                    .forDevice(deviceId)
-                    .forTable(TABLE_ACCESS_TYPE)
-                    .makePermanent()
-                    .build();
-
-            processFlowRule(install, flowRuleDirect);
-        });
-    }
-
-    /**
-     * Populates drop rules that does not match any direct access rules but has
-     * destination to a different service network in ACCESS_TYPE table.
-     *
-     * @param dstRange destination ip range
-     * @param install true to install or false to remove
-     */
-    private void populateServiceIsolationRule(Ip4Prefix dstRange, boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(dstRange)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .drop()
-                .build();
-
-        getVirtualSwitches().stream().forEach(deviceId -> {
-            FlowRule flowRuleDirect = DefaultFlowRule.builder()
-                    .fromApp(appId)
-                    .withSelector(selector)
-                    .withTreatment(treatment)
-                    .withPriority(LOW_PRIORITY)
-                    .forDevice(deviceId)
-                    .forTable(TABLE_ACCESS_TYPE)
-                    .makePermanent()
-                    .build();
-
-            processFlowRule(install, flowRuleDirect);
-        });
-    }
-
-    /**
-     * Populates indirect service access rules for ACCESS_TYPE table.
-     * These rules are installed to all devices.
-     *
-     * @param srcRange source range
-     * @param serviceIp service ip
-     * @param outGroups list of output group
-     * @param install true to install or false to remove
-     */
-    private void populateIndirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
-                                            Map<DeviceId, GroupId> outGroups, boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPSrc(srcRange)
-                .matchIPDst(serviceIp.toIpPrefix())
-                .build();
-
-        for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                    .group(outGroup.getValue())
-                    .build();
-
-            FlowRule flowRule = DefaultFlowRule.builder()
-                    .fromApp(appId)
-                    .withSelector(selector)
-                    .withTreatment(treatment)
-                    .withPriority(HIGH_PRIORITY)
-                    .forDevice(outGroup.getKey())
-                    .forTable(TABLE_ACCESS_TYPE)
-                    .makePermanent()
-                    .build();
-
-            processFlowRule(install, flowRule);
-        }
-    }
-
-    /**
-     * Populates flow rules for IN_SERVICE table.
-     *
-     * @param inPorts list of inports related to the service for each device
-     * @param outGroups set of output groups
-     * @param install true to install or false to remove
-     */
-    private void populateInServiceRule(Map<DeviceId, Set<PortNumber>> inPorts,
-                                       Map<DeviceId, GroupId> outGroups, boolean install) {
-        checkNotNull(inPorts);
-        checkNotNull(outGroups);
-
-        for (Map.Entry<DeviceId, Set<PortNumber>> entry : inPorts.entrySet()) {
-            Set<PortNumber> ports = entry.getValue();
-            DeviceId deviceId = entry.getKey();
-
-            GroupId groupId = outGroups.get(deviceId);
-            if (groupId == null) {
-                continue;
-            }
-
-            ports.stream().forEach(port -> {
-                TrafficSelector selector = DefaultTrafficSelector.builder()
-                        .matchInPort(port)
-                        .build();
-
-                TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                        .group(groupId)
-                        .build();
-
-                FlowRule flowRule = DefaultFlowRule.builder()
-                        .fromApp(appId)
-                        .withSelector(selector)
-                        .withTreatment(treatment)
-                        .withPriority(DEFAULT_PRIORITY)
-                        .forDevice(deviceId)
-                        .forTable(TABLE_IN_SERVICE)
-                        .makePermanent()
-                        .build();
-
-                processFlowRule(install, flowRule);
-            });
-        }
-    }
-
-    /**
-     * Populates flow rules for DST_IP table.
-     *
-     * @param deviceId device id
-     * @param inPort in port
-     * @param dstMac mac address
-     * @param dstIp destination ip
-     * @param tunnelId tunnel id
-     * @param tunnelIp tunnel remote ip
-     * @param install true to install or false to remove
-     */
-    private void populateDstIpRule(DeviceId deviceId, PortNumber inPort, MacAddress dstMac,
-                                   IpAddress dstIp, long tunnelId, IpAddress tunnelIp,
-                                   boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(dstIp.toIpPrefix())
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setEthDst(dstMac)
-                .setOutput(inPort)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_DST_IP)
-                .makePermanent()
-                .build();
-
-        processFlowRule(install, flowRule);
-
-        for (DeviceId vSwitchId : getVirtualSwitches()) {
-            if (vSwitchId.equals(deviceId)) {
-                continue;
-            }
-
-            ExtensionTreatment tunnelDst = getTunnelDst(vSwitchId, tunnelIp.getIp4Address());
-            if (tunnelDst == null) {
-                continue;
-            }
-
-            treatment = DefaultTrafficTreatment.builder()
-                    .setEthDst(dstMac)
-                    .setTunnelId(tunnelId)
-                    .extension(tunnelDst, vSwitchId)
-                    .setOutput(getTunnelPort(vSwitchId))
-                    .build();
-
-            flowRule = DefaultFlowRule.builder()
-                    .fromApp(appId)
-                    .withSelector(selector)
-                    .withTreatment(treatment)
-                    .withPriority(DEFAULT_PRIORITY)
-                    .forDevice(vSwitchId)
-                    .forTable(TABLE_DST_IP)
-                    .makePermanent()
-                    .build();
-
-            processFlowRule(install, flowRule);
-        }
-    }
-
-    /**
-     * Populates flow rules for TUNNEL_ID table.
-     *
-     * @param deviceId device id
-     * @param inPort in port
-     * @param mac mac address
-     * @param tunnelId tunnel id
-     * @param install true to install or false to remove
-     */
-    private void populateTunnelInRule(DeviceId deviceId, PortNumber inPort, MacAddress mac,
-                                      long tunnelId, boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchTunnelId(tunnelId)
-                .matchEthDst(mac)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(inPort)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(DEFAULT_PRIORITY)
-                .forDevice(deviceId)
-                .forTable(TABLE_TUNNEL_IN)
-                .makePermanent()
-                .build();
-
-        processFlowRule(install, flowRule);
-    }
-
-    /**
-     * Installs or uninstall a given rule.
-     *
-     * @param install true to install, false to uninstall
-     * @param rule rule
-     */
-    private void processFlowRule(boolean install, FlowRule rule) {
-        FlowRuleOperations.Builder oBuilder = FlowRuleOperations.builder();
-        oBuilder = install ? oBuilder.add(rule) : oBuilder.remove(rule);
-
-        flowRuleService.apply(oBuilder.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.error(String.format("Failed %s, %s", ops.toString(), rule.toString()));
-            }
-        }));
-    }
-
-    /**
-     * Returns tunnel port of the device.
-     *
-     * @param deviceId device id
-     * @return tunnel port number, or null if no tunnel port exists on a given device
-     */
-    private PortNumber getTunnelPort(DeviceId deviceId) {
-        Port port = deviceService.getPorts(deviceId).stream()
-                .filter(p -> p.annotations().value(PORT_NAME).contains(tunnelType))
-                .findFirst().orElse(null);
-
-        return port == null ? null : port.number();
-    }
-
-    /**
-     * Returns data plane interface port name of a given device.
-     *
-     * @param deviceId device id
-     * @param dpIntf data plane interface port name
-     * @return data plane interface port number, or null if no such port exists
-     */
-    private PortNumber getDpPort(DeviceId deviceId, String dpIntf) {
-        Port port = deviceService.getPorts(deviceId).stream()
-                .filter(p -> p.annotations().value(PORT_NAME).contains(dpIntf) &&
-                        p.isEnabled())
-                .findFirst().orElse(null);
-
-        return port == null ? null : port.number();
-    }
-
-    /** Returns data plane interface port number of a given host.
-     *
-     * @param host host
-     * @return port number, or null
-     */
-    private PortNumber getDpPort(Host host) {
-        String portName = host.annotations().value(DATA_PLANE_INTF);
-        return portName == null ? null : getDpPort(host.location().deviceId(), portName);
-    }
-
-    /**
-     * Returns service vlan from a given host.
-     *
-     * @param host host
-     * @return vlan id, or null
-     */
-    private VlanId getServiceVlan(Host host) {
-        String serviceVlan = host.annotations().value(S_TAG);
-        return serviceVlan == null ? null : VlanId.vlanId(Short.parseShort(serviceVlan));
-    }
-
-    /**
-     * Returns IP address for tunneling for a given host.
-     *
-     * @param host host
-     * @return ip address, or null
-     */
-    private IpAddress getTunnelIp(Host host) {
-        String ip = host.annotations().value(DATA_PLANE_IP);
-        return ip == null ? null : IpAddress.valueOf(ip);
-    }
-
-
-    /**
-     * Returns the destination IP from a given flow rule if the rule contains
-     * the match of it.
-     *
-     * @param flowRule flow rule
-     * @return ip prefix, or null if the rule doesn't have ip match
-     */
-    private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
-        Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
-        if (criterion != null && criterion instanceof IPCriterion) {
-            IPCriterion ip = (IPCriterion) criterion;
-            return ip.ip();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the output port number from a given flow rule.
-     *
-     * @param flowRule flow rule
-     * @return port number, or null if the rule does not have output instruction
-     */
-    private PortNumber getOutputFromTreatment(FlowRule flowRule) {
-        Instruction instruction = flowRule.treatment().allInstructions().stream()
-                .filter(inst -> inst instanceof Instructions.OutputInstruction)
-                .findFirst()
-                .orElse(null);
-
-        if (instruction == null) {
-            return null;
-        }
-
-        return ((Instructions.OutputInstruction) instruction).port();
-    }
-
-    /**
-     * Returns if a given flow rule has vlan push instruction or not.
-     *
-     * @param flowRule flow rule
-     * @return true if it includes vlan push, or false
-     */
-    private boolean isVlanPushFromTreatment(FlowRule flowRule) {
-        Instruction instruction = flowRule.treatment().allInstructions().stream()
-                .filter(inst -> inst instanceof L2ModificationInstruction)
-                .filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
-                .findAny()
-                .orElse(null);
-
-        return instruction != null;
-    }
-
-    /**
-     * Creates a new group for a given service.
-     *
-     * @param deviceId device id to create a group
-     * @param service cord service
-     * @return group id, or null if it fails to create
-     */
-    private GroupId createServiceGroup(DeviceId deviceId, VtnService service) {
-        checkNotNull(service);
-
-        GroupKey groupKey = getGroupKey(service.id());
-        Group group = groupService.getGroup(deviceId, groupKey);
-        GroupId groupId = getGroupId(service.id(), deviceId);
-
-        if (group != null) {
-            log.debug("Group {} is already exist in {}", service.id(), deviceId);
-            return groupId;
-        }
-
-        GroupBuckets buckets = getServiceGroupBuckets(
-                deviceId, service.vni(), getInstances(service.id()));
-        GroupDescription groupDescription = new DefaultGroupDescription(
-                deviceId,
-                GroupDescription.Type.SELECT,
-                buckets,
-                groupKey,
-                groupId.id(),
-                appId);
-
-        groupService.addGroup(groupDescription);
-
-        return groupId;
-    }
-
-    /**
-     * Returns group buckets for a given device.
-     *
-     * @param deviceId device id
-     * @param tunnelId tunnel id
-     * @param hosts list of host
-     * @return group buckets
-     */
-    private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId,
-                                                Set<Host> hosts) {
-        List<GroupBucket> buckets = Lists.newArrayList();
-
-        hosts.stream().forEach(host -> {
-            Ip4Address tunnelIp = getTunnelIp(host).getIp4Address();
-            DeviceId hostDevice = host.location().deviceId();
-
-            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
-                    .builder()
-                    .setEthDst(host.mac());
-            if (deviceId.equals(hostDevice)) {
-                tBuilder.setOutput(host.location().port());
-            } else {
-                ExtensionTreatment tunnelDst = getTunnelDst(deviceId, tunnelIp);
-                tBuilder.extension(tunnelDst, deviceId)
-                        .setTunnelId(tunnelId)
-                        .setOutput(getTunnelPort(hostDevice));
-            }
-            buckets.add(DefaultGroupBucket.createSelectGroupBucket(tBuilder.build()));
-        });
-
-        return new GroupBuckets(buckets);
-    }
-
-    /**
-     * Returns globally unique group ID.
-     *
-     * @param serviceId service id
-     * @param deviceId device id
-     * @return group id
-     */
-    private GroupId getGroupId(VtnServiceId serviceId, DeviceId deviceId) {
-        return new DefaultGroupId(Objects.hash(serviceId, deviceId));
-    }
-
-    /**
-     * Returns group key of a service.
-     *
-     * @param serviceId service id
-     * @return group key
-     */
-    private GroupKey getGroupKey(VtnServiceId serviceId) {
-        return new DefaultGroupKey(serviceId.id().getBytes());
-    }
-
-    /**
-     * Returns extension instruction to set tunnel destination.
-     *
-     * @param deviceId device id
-     * @param remoteIp tunnel destination address
-     * @return extension treatment or null if it fails to get instruction
-     */
-    private ExtensionTreatment getTunnelDst(DeviceId deviceId, Ip4Address remoteIp) {
-        try {
-            Device device = deviceService.getDevice(deviceId);
-
-            if (device.is(ExtensionTreatmentResolver.class)) {
-                ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
-                ExtensionTreatment treatment =
-                        resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
-                treatment.setPropertyValue("tunnelDst", remoteIp);
-
-                return treatment;
-            } else {
-                log.warn("The extension treatment resolving behaviour is not supported in device {}",
-                        device.id().toString());
-                return null;
-            }
-        } catch (ItemNotFoundException | UnsupportedOperationException |
-                ExtensionPropertyException e) {
-            log.error("Failed to get extension instruction {}", deviceId);
-            return null;
-        }
-    }
-
-    /**
-     * Returns integration bridges configured in the system.
-     *
-     * @return set of device ids
-     */
-    private Set<DeviceId> getVirtualSwitches() {
-        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
-        if (config == null) {
-            log.debug("No configuration found for {}", appId.name());
-            return Sets.newHashSet();
-        }
-
-        return config.cordVtnNodes().stream()
-                .map(CordVtnNode::intBrId).collect(Collectors.toSet());
-    }
-
-    /**
-     * Returns instances with a given network service.
-     *
-     * @param serviceId service id
-     * @return set of hosts
-     */
-    private Set<Host> getInstances(VtnServiceId serviceId) {
-        return StreamSupport.stream(hostService.getHosts().spliterator(), false)
-                .filter(host -> Objects.equals(
-                        serviceId.id(),
-                        host.annotations().value(SERVICE_ID)))
-                .collect(Collectors.toSet());
-    }
-}
-
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/DummyInstanceHandler.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/DummyInstanceHandler.java
new file mode 100644
index 0000000..ee78218
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/DummyInstanceHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl.service;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
+import org.onosproject.xosclient.api.VtnService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Provides network connectivity for dummy service instances.
+ */
+@Component(immediate = true)
+public class DummyInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
+
+    @Activate
+    protected void activate() {
+        serviceType = VtnService.ServiceType.DUMMY;
+        eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-dummy", "event-handler"));
+        super.activate();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+    }
+
+    @Override
+    public void instanceDetected(Instance instance) {
+        super.instanceDetected(instance);
+    }
+
+    @Override
+    public void instanceRemoved(Instance instance) {
+        super.instanceRemoved(instance);
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/OltAgentInstanceHandler.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/OltAgentInstanceHandler.java
new file mode 100644
index 0000000..eaec689
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/OltAgentInstanceHandler.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl.service;
+
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cordconfig.access.AccessAgentConfig;
+import org.onosproject.cordconfig.access.AccessAgentData;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.xosclient.api.VtnService;
+
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.PRIORITY_MANAGEMENT;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.TABLE_ACCESS_TYPE;
+
+/**
+ * Provides network connectivity for OLT agent instances.
+ */
+@Component(immediate = true)
+public class OltAgentInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
+
+    private static final Class<AccessAgentConfig> CONFIG_CLASS = AccessAgentConfig.class;
+    private ConfigFactory<DeviceId, AccessAgentConfig> configFactory =
+            new ConfigFactory<DeviceId, AccessAgentConfig>(
+                    SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessAgent") {
+                @Override
+                public AccessAgentConfig createConfig() {
+                    return new AccessAgentConfig();
+                }
+            };
+
+    private Map<DeviceId, AccessAgentData> oltAgentData = Maps.newConcurrentMap();
+    private IpPrefix mgmtIpRange = null;
+
+    @Activate
+    protected void activate() {
+        eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-olt", "event-handler"));
+        serviceType = VtnService.ServiceType.OLT_AGENT;
+
+        configRegistry.registerConfigFactory(configFactory);
+        configListener = new InternalConfigListener();
+
+        super.activate();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+    }
+
+    @Override
+    public void instanceDetected(Instance instance) {
+        log.info("OLT agent instance detected {}", instance);
+
+        managementAccessRule(instance.deviceId(), true);
+        // TODO implement
+    }
+
+    @Override
+    public void instanceRemoved(Instance instance) {
+        log.info("OLT agent instance removed {}", instance);
+
+        if (getInstances(instance.serviceId()).isEmpty()) {
+            nodeManager.completeNodes().stream().forEach(node ->
+                managementAccessRule(node.intBrId(), false));
+        }
+
+        // TODO implement
+    }
+
+    private void managementAccessRule(DeviceId deviceId, boolean install) {
+        // TODO remove this rule after long term management network is done
+        if (mgmtIpRange != null) {
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(mgmtIpRange)
+                    .build();
+
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(PortNumber.LOCAL)
+                    .build();
+
+            FlowRule flowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_MANAGEMENT)
+                    .forDevice(deviceId)
+                    .forTable(TABLE_ACCESS_TYPE)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, flowRule);
+        }
+    }
+
+    private void readAccessAgentConfig() {
+
+        Set<DeviceId> deviceSubjects = configRegistry.getSubjects(DeviceId.class, CONFIG_CLASS);
+        deviceSubjects.stream().forEach(subject -> {
+            AccessAgentConfig config = configRegistry.getConfig(subject, CONFIG_CLASS);
+            if (config != null) {
+                oltAgentData.put(subject, config.getAgent());
+            }
+        });
+    }
+
+    @Override
+    protected void readConfiguration() {
+        CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+        if (config == null) {
+            log.debug("No configuration found");
+            return;
+        }
+
+        osAccess = config.openstackAccess();
+        xosAccess = config.xosAccess();
+        mgmtIpRange = config.managementIpRange();
+    }
+
+    public class InternalConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+
+            switch (event.type()) {
+                case CONFIG_UPDATED:
+                case CONFIG_ADDED:
+                    if (event.configClass().equals(CordVtnConfig.class)) {
+                        readConfiguration();
+                    } else if (event.configClass().equals(CONFIG_CLASS)) {
+                        readAccessAgentConfig();
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/VsgInstanceHandler.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/VsgInstanceHandler.java
new file mode 100644
index 0000000..f60f72b
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/VsgInstanceHandler.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl.service;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceManager;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.HostId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.xosclient.api.VtnPort;
+import org.onosproject.xosclient.api.VtnPortApi;
+import org.onosproject.xosclient.api.VtnPortId;
+import org.onosproject.xosclient.api.VtnService;
+
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.api.Instance.*;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
+
+/**
+ * Provides network connectivity for vSG instances.
+ */
+@Component(immediate = true)
+@Service(value = VsgInstanceHandler.class)
+public final class VsgInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
+
+    private static final String STAG = "stag";
+    private static final String VSG_VM = "vsgVm";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnInstanceManager instanceManager;
+
+    @Activate
+    protected void activate() {
+        serviceType = VtnService.ServiceType.VSG;
+        eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-vsg", "event-handler"));
+        super.activate();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        super.deactivate();
+    }
+
+    @Override
+    public void instanceDetected(Instance instance) {
+        if (isVsgContainer(instance)) {
+            log.info("vSG container detected {}", instance);
+
+            // find vsg vm for this vsg container
+            String vsgVmId = instance.getAnnotation(VSG_VM);
+            if (Strings.isNullOrEmpty(vsgVmId)) {
+                log.warn("Failed to find VSG VM for {}", instance);
+                return;
+            }
+
+            Instance vsgVm = Instance.of(hostService.getHost(HostId.hostId(vsgVmId)));
+            VtnPort vtnPort = getVtnPort(vsgVm);
+            if (vtnPort == null || getStag(vtnPort) == null) {
+                return;
+            }
+
+            populateVsgRules(vsgVm, getStag(vtnPort),
+                             nodeManager.dpPort(vsgVm.deviceId()),
+                             vtnPort.addressPairs().keySet(),
+                             true);
+
+        } else {
+            VtnPort vtnPort = getVtnPort(instance);
+            if (vtnPort == null || getStag(vtnPort) == null) {
+                return;
+            }
+
+            vtnPort.addressPairs().entrySet().stream()
+                    .forEach(pair -> addVsgContainer(
+                            instance,
+                            pair.getKey(),
+                            pair.getValue(),
+                            getStag(vtnPort).toString()
+                    ));
+            super.instanceDetected(instance);
+        }
+    }
+
+    @Override
+    public void instanceRemoved(Instance instance) {
+        if (isVsgContainer(instance)) {
+            log.info("vSG container vanished {}", instance);
+
+            // find vsg vm for this vsg container
+            String vsgVmId = instance.getAnnotation(VSG_VM);
+            if (Strings.isNullOrEmpty(vsgVmId)) {
+                log.warn("Failed to find VSG VM for {}", instance);
+                return;
+            }
+
+            Instance vsgVm = Instance.of(hostService.getHost(HostId.hostId(vsgVmId)));
+            VtnPort vtnPort = getVtnPort(vsgVm);
+            if (vtnPort == null || getStag(vtnPort) == null) {
+                return;
+            }
+
+            populateVsgRules(vsgVm, getStag(vtnPort),
+                             nodeManager.dpPort(vsgVm.deviceId()),
+                             vtnPort.addressPairs().keySet(),
+                             false);
+
+        } else {
+            // TODO remove vsg vm related rules
+            super.instanceRemoved(instance);
+        }
+    }
+
+    /**
+     * Updates set of vSGs in a given vSG VM.
+     *
+     * @param vsgVmId vsg vm host id
+     * @param stag stag
+     * @param vsgInstances full set of vsg wan ip and mac address pairs in this vsg vm
+     */
+    public void updateVsgInstances(HostId vsgVmId, String stag, Map<IpAddress, MacAddress> vsgInstances) {
+        if (hostService.getHost(vsgVmId) == null) {
+            log.debug("vSG VM {} is not added yet, ignore this update", vsgVmId);
+            return;
+        }
+
+        Instance vsgVm = Instance.of(hostService.getHost(vsgVmId));
+        if (vsgVm == null) {
+            log.warn("Failed to find existing vSG VM for STAG: {}", stag);
+            return;
+        }
+
+        log.info("Updates vSGs in {} with STAG: {}", vsgVm, stag);
+
+        // adds vSGs in the address pair
+        vsgInstances.entrySet().stream()
+                .filter(addr -> hostService.getHostsByMac(addr.getValue()).isEmpty())
+                .forEach(addr -> addVsgContainer(
+                        vsgVm,
+                        addr.getKey(),
+                        addr.getValue(),
+                        stag));
+
+        // removes vSGs not listed in the address pair
+        hostService.getConnectedHosts(vsgVm.host().location()).stream()
+                .filter(host -> !host.mac().equals(vsgVm.mac()))
+                .filter(host -> !vsgInstances.values().contains(host.mac()))
+                .forEach(host -> {
+                    log.info("Removed vSG {}", host.toString());
+                    instanceManager.removeInstance(host.id());
+                });
+    }
+
+    private boolean isVsgContainer(Instance instance) {
+        return !Strings.isNullOrEmpty(instance.host().annotations().value(STAG));
+    }
+
+    private void addVsgContainer(Instance vsgVm, IpAddress vsgWanIp, MacAddress vsgMac,
+                                 String stag) {
+        HostId hostId = HostId.hostId(vsgMac);
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(SERVICE_TYPE, vsgVm.serviceType().toString())
+                .set(SERVICE_ID, vsgVm.serviceId().id())
+                .set(PORT_ID, vsgVm.portId().id())
+                .set(NESTED_INSTANCE, TRUE)
+                .set(STAG, stag)
+                .set(VSG_VM, vsgVm.host().id().toString())
+                .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                vsgMac,
+                VlanId.NONE,
+                vsgVm.host().location(),
+                Sets.newHashSet(vsgWanIp),
+                annotations.build());
+
+        instanceManager.addInstance(hostId, hostDesc);
+    }
+
+    private void populateVsgRules(Instance vsgVm, VlanId stag, PortNumber dpPort,
+                                  Set<IpAddress> vsgWanIps, boolean install) {
+        // for traffics with s-tag, strip the tag and take through the vSG VM
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(dpPort)
+                .matchVlanId(stag)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(vsgVm.portNumber())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(vsgVm.deviceId())
+                .forTable(TABLE_VLAN)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        // for traffics with customer vlan, tag with the service vlan based on input port with
+        // lower priority to avoid conflict with WAN tag
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(vsgVm.portNumber())
+                .matchVlanId(stag)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dpPort)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_DEFAULT)
+                .forDevice(vsgVm.deviceId())
+                .forTable(TABLE_VLAN)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        // for traffic coming from WAN, tag 500 and take through the vSG VM
+        // based on destination ip
+        vsgWanIps.stream().forEach(ip -> {
+            TrafficSelector downstream = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(ip.toIpPrefix())
+                    .build();
+
+            TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
+                    .pushVlan()
+                    .setVlanId(VLAN_WAN)
+                    .setEthDst(vsgVm.mac())
+                    .setOutput(vsgVm.portNumber())
+                    .build();
+
+            FlowRule downstreamFlowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(downstream)
+                    .withTreatment(downstreamTreatment)
+                    .withPriority(PRIORITY_DEFAULT)
+                    .forDevice(vsgVm.deviceId())
+                    .forTable(TABLE_DST_IP)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, downstreamFlowRule);
+        });
+
+        // remove downstream flow rules for the vSG not shown in vsgWanIps
+        for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
+            if (!rule.deviceId().equals(vsgVm.deviceId())) {
+                continue;
+            }
+            PortNumber output = getOutputFromTreatment(rule);
+            if (output == null || !output.equals(vsgVm.portNumber()) ||
+                    !isVlanPushFromTreatment(rule)) {
+                continue;
+            }
+
+            IpPrefix dstIp = getDstIpFromSelector(rule);
+            if (dstIp != null && !vsgWanIps.contains(dstIp.address())) {
+                pipeline.processFlowRule(false, rule);
+            }
+        }
+    }
+
+    private VtnPort getVtnPort(Instance instance) {
+        checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+        checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+        VtnPortId vtnPortId = instance.portId();
+        VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
+        VtnPort vtnPort = portApi.vtnPort(vtnPortId, osAccess);
+        if (vtnPort == null) {
+            log.warn("Failed to get port information of {}", instance);
+            return null;
+        }
+        return vtnPort;
+    }
+
+    // TODO get stag from XOS when XOS provides it, extract if from port name for now
+    private VlanId getStag(VtnPort vtnPort) {
+        checkNotNull(vtnPort);
+
+        String portName = vtnPort.name();
+        if (portName != null && portName.startsWith(STAG)) {
+            return VlanId.vlanId(portName.split("-")[1]);
+        } else {
+            return null;
+        }
+    }
+
+    private PortNumber getOutputFromTreatment(FlowRule flowRule) {
+        Instruction instruction = flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof Instructions.OutputInstruction)
+                .findFirst()
+                .orElse(null);
+        if (instruction == null) {
+            return null;
+        }
+        return ((Instructions.OutputInstruction) instruction).port();
+    }
+
+    private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
+        Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
+        if (criterion != null && criterion instanceof IPCriterion) {
+            IPCriterion ip = (IPCriterion) criterion;
+            return ip.ip();
+        } else {
+            return null;
+        }
+    }
+
+    private boolean isVlanPushFromTreatment(FlowRule flowRule) {
+        Instruction instruction = flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof L2ModificationInstruction)
+                .filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
+                .findAny()
+                .orElse(null);
+        return instruction != null;
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/package-info.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/package-info.java
new file mode 100644
index 0000000..035b012
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/impl/service/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of instance handlers for various network services.
+ */
+package org.onosproject.cordvtn.impl.service;
\ No newline at end of file
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
index 17d48a5..bf8d79d 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
@@ -18,9 +18,10 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.Maps;
+import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
-import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.impl.service.VsgInstanceHandler;
 import org.onosproject.net.HostId;
 import org.onosproject.rest.AbstractWebResource;
 import org.slf4j.Logger;
@@ -57,7 +58,7 @@
     private static final String STAG_PREFIX = "stag-";
     private static final int STAG_BEGIN_INDEX = 5;
 
-    private final CordVtnService service = get(CordVtnService.class);
+    private final VsgInstanceHandler service = DefaultServiceDirectory.getService(VsgInstanceHandler.class);
 
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
@@ -74,6 +75,7 @@
     public Response updatePorts(@PathParam("id") String id, InputStream input) {
         log.debug(String.format(PORTS_MESSAGE, "update"));
 
+        // TODO get vSG updates from XOS to CORD VTN service directly
         try {
             ObjectMapper mapper = new ObjectMapper();
             JsonNode jsonNode = mapper.readTree(input).get(PORT);
@@ -88,17 +90,16 @@
 
             // this is allowed address pairs updates
             MacAddress mac = MacAddress.valueOf(jsonNode.path(MAC_ADDRESS).asText());
-            Map<IpAddress, MacAddress> vSgs = Maps.newHashMap();
+            Map<IpAddress, MacAddress> vsgInstances = Maps.newHashMap();
             jsonNode.path(ADDRESS_PAIRS).forEach(addrPair -> {
                 IpAddress pairIp = IpAddress.valueOf(addrPair.path(IP_ADDERSS).asText());
                 MacAddress pairMac = MacAddress.valueOf(addrPair.path(MAC_ADDRESS).asText());
-                vSgs.put(pairIp, pairMac);
+                vsgInstances.put(pairIp, pairMac);
             });
 
-            service.updateVirtualSubscriberGateways(
-                    HostId.hostId(mac),
-                    name.substring(STAG_BEGIN_INDEX),
-                    vSgs);
+            service.updateVsgInstances(HostId.hostId(mac),
+                                       name.substring(STAG_BEGIN_INDEX),
+                                       vsgInstances);
         } catch (Exception e) {
             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
         }