Placed API and implementation into separate packages

Change-Id: If8a9223a7a225db1b2aa2d09738857af482736bc
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtnRuleInstaller.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtnRuleInstaller.java
new file mode 100644
index 0000000..8652e18
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtnRuleInstaller.java
@@ -0,0 +1,1539 @@
+/*
+ * 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.CordService;
+import org.onosproject.cordvtn.api.CordServiceId;
+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.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.driver.DefaultDriverData;
+import org.onosproject.net.driver.DefaultDriverHandler;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+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.EthCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+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.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
+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.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+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 static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IN_PORT;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_SRC;
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_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 S_TAG = "stag";
+
+    private final ApplicationId appId;
+    private final FlowRuleService flowRuleService;
+    private final DeviceService deviceService;
+    private final DriverService driverService;
+    private final GroupService groupService;
+    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 driverService driver service
+     * @param groupService group service
+     * @param configRegistry config registry
+     * @param tunnelType tunnel type
+     */
+    public CordVtnRuleInstaller(ApplicationId appId,
+                                FlowRuleService flowRuleService,
+                                DeviceService deviceService,
+                                DriverService driverService,
+                                GroupService groupService,
+                                NetworkConfigRegistry configRegistry,
+                                String tunnelType) {
+        this.appId = appId;
+        this.flowRuleService = flowRuleService;
+        this.deviceService = deviceService;
+        this.driverService = driverService;
+        this.groupService = groupService;
+        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 tunnelIp tunnel ip
+     * @param vNet openstack network
+     */
+    public void populateBasicConnectionRules(Host host, IpAddress tunnelIp, OpenstackNetwork vNet) {
+        checkNotNull(host);
+        checkNotNull(vNet);
+
+        DeviceId deviceId = host.location().deviceId();
+        PortNumber inPort = host.location().port();
+        MacAddress dstMac = host.mac();
+        IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
+        long tunnelId = Long.parseLong(vNet.segmentId());
+
+        OpenstackSubnet subnet = vNet.subnets().stream()
+                .findFirst()
+                .orElse(null);
+
+        if (subnet == null) {
+            log.error("Failed to get subnet for {}", host.id());
+            return;
+        }
+
+        populateLocalInPortRule(deviceId, inPort, hostIp);
+        populateDirectAccessRule(Ip4Prefix.valueOf(subnet.cidr()), Ip4Prefix.valueOf(subnet.cidr()));
+        populateServiceIsolationRule(Ip4Prefix.valueOf(subnet.cidr()));
+        populateDstIpRule(deviceId, inPort, dstMac, hostIp, tunnelId, tunnelIp);
+        populateTunnelInRule(deviceId, inPort, dstMac, tunnelId);
+    }
+
+    /**
+     * Removes all rules related to a given service VM host.
+     *
+     * @param host host to be removed
+     */
+    public void removeBasicConnectionRules(Host host) {
+        checkNotNull(host);
+
+        DeviceId deviceId = host.location().deviceId();
+        MacAddress mac = host.mac();
+        PortNumber port = host.location().port();
+        IpAddress ip = host.ipAddresses().stream().findFirst().orElse(null);
+
+        for (FlowRule flowRule : flowRuleService.getFlowRulesById(appId)) {
+            if (flowRule.deviceId().equals(deviceId)) {
+                PortNumber inPort = getInPort(flowRule);
+                if (inPort != null && inPort.equals(port)) {
+                    processFlowRule(false, flowRule);
+                    continue;
+                }
+
+                PortNumber output = getOutputFromTreatment(flowRule);
+                if (output != null && output.equals(host.location().port())) {
+                    processFlowRule(false, flowRule);
+                }
+            }
+
+            MacAddress dstMac = getDstMacFromTreatment(flowRule);
+            if (dstMac != null && dstMac.equals(mac)) {
+                processFlowRule(false, flowRule);
+                continue;
+            }
+
+            dstMac = getDstMacFromSelector(flowRule);
+            if (dstMac != null && dstMac.equals(mac)) {
+                processFlowRule(false, flowRule);
+                continue;
+            }
+
+            IpPrefix dstIp = getDstIpFromSelector(flowRule);
+            if (dstIp != null && dstIp.equals(ip.toIpPrefix())) {
+                processFlowRule(false, flowRule);
+            }
+        }
+
+        // TODO uninstall same network access rule in access table if no vm exists in the network
+    }
+
+    /**
+     * Populates service dependency rules.
+     *
+     * @param tService tenant cord service
+     * @param pService provider cord service
+     * @param isBidirectional true to enable bidirectional connection between two services
+     */
+    public void populateServiceDependencyRules(CordService tService, CordService pService,
+                                               boolean isBidirectional) {
+        checkNotNull(tService);
+        checkNotNull(pService);
+
+        Ip4Prefix srcRange = tService.serviceIpRange().getIp4Prefix();
+        Ip4Prefix dstRange = pService.serviceIpRange().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> vms = tService.hosts().keySet()
+                    .stream()
+                    .filter(host -> host.location().deviceId().equals(deviceId))
+                    .map(host -> host.location().port())
+                    .collect(Collectors.toSet());
+            inPorts.put(deviceId, vms);
+        });
+
+        populateIndirectAccessRule(srcRange, serviceIp, outGroups);
+        populateDirectAccessRule(srcRange, dstRange);
+        if (isBidirectional) {
+            populateDirectAccessRule(dstRange, srcRange);
+        }
+        populateInServiceRule(inPorts, outGroups);
+    }
+
+    /**
+     * Removes service dependency rules.
+     *
+     * @param tService tenant cord service
+     * @param pService provider cord service
+     */
+    public void removeServiceDependencyRules(CordService tService, CordService pService) {
+        checkNotNull(tService);
+        checkNotNull(pService);
+
+        Ip4Prefix srcRange = tService.serviceIpRange().getIp4Prefix();
+        Ip4Prefix dstRange = pService.serviceIpRange().getIp4Prefix();
+        IpPrefix serviceIp = pService.serviceIp().toIpPrefix();
+
+        Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
+        GroupKey groupKey = new DefaultGroupKey(pService.id().id().getBytes());
+
+        getVirtualSwitches().stream().forEach(deviceId -> {
+            Group group = groupService.getGroup(deviceId, groupKey);
+            if (group != null) {
+                outGroups.put(deviceId, group.id());
+            }
+        });
+
+        for (FlowRule flowRule : flowRuleService.getFlowRulesById(appId)) {
+            IpPrefix dstIp = getDstIpFromSelector(flowRule);
+            IpPrefix srcIp = getSrcIpFromSelector(flowRule);
+
+            if (dstIp != null && dstIp.equals(serviceIp)) {
+                processFlowRule(false, flowRule);
+                continue;
+            }
+
+            if (dstIp != null && srcIp != null) {
+                if (dstIp.equals(dstRange) && srcIp.equals(srcRange)) {
+                    processFlowRule(false, flowRule);
+                    continue;
+                }
+
+                if (dstIp.equals(srcRange) && srcIp.equals(dstRange)) {
+                    processFlowRule(false, flowRule);
+                    continue;
+                }
+            }
+
+            GroupId groupId = getGroupIdFromTreatment(flowRule);
+            if (groupId != null && groupId.equals(outGroups.get(flowRule.deviceId()))) {
+                processFlowRule(false, flowRule);
+            }
+        }
+
+        // TODO remove the group if it is not in use
+    }
+
+    /**
+     * Updates group buckets for a given service to all devices.
+     *
+     * @param service cord service
+     */
+    public void updateServiceGroup(CordService 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.segmentationId(), service.hosts()).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);
+            }
+        }
+    }
+
+    /**
+     * 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, CordService 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.serviceIpRange())
+                .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);
+    }
+
+    /**
+     * Removes management network access rules.
+     *
+     * @param host host to be removed
+     * @param mService service for management network
+     */
+    public void removeManagementNetworkRules(Host host, CordService mService) {
+        checkNotNull(mService);
+        // TODO remove management network specific rules
+    }
+
+    /**
+     * 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
+     */
+    private void populateLocalInPortRule(DeviceId deviceId, PortNumber inPort, IpAddress srcIp) {
+        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(true, 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(true, 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
+     */
+    private void populateDirectAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange) {
+        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(true, 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
+     */
+    private void populateServiceIsolationRule(Ip4Prefix dstRange) {
+        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(true, 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
+     */
+    private void populateIndirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
+                                            Map<DeviceId, GroupId> outGroups) {
+        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(true, 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
+     */
+    private void populateInServiceRule(Map<DeviceId, Set<PortNumber>> inPorts, Map<DeviceId, GroupId> outGroups) {
+        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(true, 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
+     */
+    private void populateDstIpRule(DeviceId deviceId, PortNumber inPort, MacAddress dstMac,
+                                   IpAddress dstIp, long tunnelId, IpAddress tunnelIp) {
+        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(true, 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(true, flowRule);
+        }
+    }
+
+    /**
+     * Populates flow rules for TUNNEL_ID table.
+     *
+     * @param deviceId device id
+     * @param inPort in port
+     * @param mac mac address
+     * @param tunnelId tunnel id
+     */
+    private void populateTunnelInRule(DeviceId deviceId, PortNumber inPort, MacAddress mac, long tunnelId) {
+        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(true, 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 the inport from a given flow rule if the rule contains the match of it.
+     *
+     * @param flowRule flow rule
+     * @return port number, or null if the rule doesn't have inport match
+     */
+    private PortNumber getInPort(FlowRule flowRule) {
+        Criterion criterion = flowRule.selector().getCriterion(IN_PORT);
+        if (criterion != null && criterion instanceof PortCriterion) {
+            PortCriterion port = (PortCriterion) criterion;
+            return port.port();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the destination mac address from a given flow rule if the rule
+     * contains the instruction of it.
+     *
+     * @param flowRule flow rule
+     * @return mac address, or null if the rule doesn't have destination mac instruction
+     */
+    private MacAddress getDstMacFromTreatment(FlowRule flowRule) {
+        Instruction instruction = flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof ModEtherInstruction &&
+                        ((ModEtherInstruction) inst).subtype().equals(ETH_DST))
+                .findFirst()
+                .orElse(null);
+
+        if (instruction == null) {
+            return null;
+        }
+
+        return ((ModEtherInstruction) instruction).mac();
+    }
+
+    /**
+     * Returns the destination mac address from a given flow rule if the rule
+     * contains the match of it.
+     *
+     * @param flowRule flow rule
+     * @return mac address, or null if the rule doesn't have destination mac match
+     */
+    private MacAddress getDstMacFromSelector(FlowRule flowRule) {
+        Criterion criterion = flowRule.selector().getCriterion(Criterion.Type.ETH_DST);
+        if (criterion != null && criterion instanceof EthCriterion) {
+            EthCriterion eth = (EthCriterion) criterion;
+            return eth.mac();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 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 source 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 getSrcIpFromSelector(FlowRule flowRule) {
+        Criterion criterion = flowRule.selector().getCriterion(IPV4_SRC);
+        if (criterion != null && criterion instanceof IPCriterion) {
+            IPCriterion ip = (IPCriterion) criterion;
+            return ip.ip();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the group ID from a given flow rule if the rule contains the
+     * treatment of it.
+     *
+     * @param flowRule flow rule
+     * @return group id, or null if the rule doesn't have group instruction
+     */
+    private GroupId getGroupIdFromTreatment(FlowRule flowRule) {
+        Instruction instruction = flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof Instructions.GroupInstruction)
+                .findFirst()
+                .orElse(null);
+
+        if (instruction == null) {
+            return null;
+        }
+
+        return ((Instructions.GroupInstruction) instruction).groupId();
+    }
+
+    /**
+     * 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, CordService 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.segmentationId(), service.hosts());
+        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, Map<Host, IpAddress> hosts) {
+        List<GroupBucket> buckets = Lists.newArrayList();
+
+        for (Map.Entry<Host, IpAddress> entry : hosts.entrySet()) {
+            Host host = entry.getKey();
+            Ip4Address remoteIp = entry.getValue().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, remoteIp);
+                if (tunnelDst == null) {
+                    continue;
+                }
+
+                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(CordServiceId 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(CordServiceId 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 {
+            Driver driver = driverService.getDriver(deviceId);
+            DefaultDriverData driverData = new DefaultDriverData(driver, deviceId);
+            DriverHandler handler = new DefaultDriverHandler(driverData);
+            ExtensionTreatmentResolver resolver = handler.behaviour(ExtensionTreatmentResolver.class);
+
+            ExtensionTreatment treatment =
+                    resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+            treatment.setPropertyValue("tunnelDst", remoteIp);
+
+            return treatment;
+        } 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());
+    }
+}
+