CORD-305 Added basic VTN rules for VMs with openstackswitching

Change-Id: I3eebc3c396b6657457363c183ca8c260b6bb8db4
diff --git a/apps/cordvtn/pom.xml b/apps/cordvtn/pom.xml
index 1d96108..3f3ec23 100644
--- a/apps/cordvtn/pom.xml
+++ b/apps/cordvtn/pom.xml
@@ -33,6 +33,10 @@
 
     <properties>
         <onos.app.name>org.onosproject.cordvtn</onos.app.name>
+        <onos.app.requires>
+            org.onosproject.ovsdb,
+            org.onosproject.openstackswitching
+        </onos.app.requires>
     </properties>
 
     <dependencies>
@@ -64,6 +68,11 @@
             <artifactId>org.apache.karaf.shell.console</artifactId>
             <version>3.0.3</version>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-app-openstackswitching-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
index c3bf77c..6729774 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.cordvtn;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -23,6 +25,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.util.ItemNotFoundException;
+import org.onlab.packet.IpAddress;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.core.ApplicationId;
@@ -31,9 +34,11 @@
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
 import org.onosproject.net.Port;
 import org.onosproject.net.behaviour.BridgeConfig;
 import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.behaviour.ControllerInfo;
 import org.onosproject.net.behaviour.DefaultTunnelDescription;
 import org.onosproject.net.behaviour.TunnelConfig;
@@ -45,9 +50,13 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverHandler;
 import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
+import org.onosproject.openstackswitching.OpenstackNetwork;
+import org.onosproject.openstackswitching.OpenstackPort;
+import org.onosproject.openstackswitching.OpenstackSwitchingService;
 import org.onosproject.ovsdb.controller.OvsdbClientService;
 import org.onosproject.ovsdb.controller.OvsdbController;
 import org.onosproject.ovsdb.controller.OvsdbNodeId;
@@ -62,8 +71,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.util.Tools.groupedThreads;
@@ -72,8 +83,8 @@
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Provides initial setup or cleanup for provisioning virtual tenant networks
- * on ovsdb, integration bridge and vm when they are added or deleted.
+ * Provisions virtual tenant networks with service chaining capability
+ * in OpenStack environment.
  */
 @Component(immediate = true)
 @Service
@@ -86,7 +97,8 @@
             .register(KryoNamespaces.API)
             .register(CordVtnNode.class)
             .register(NodeState.class);
-    private static final String DEFAULT_BRIDGE_NAME = "br-int";
+    private static final String DEFAULT_BRIDGE = "br-int";
+    private static final String VPORT_PREFIX = "tap";
     private static final String DEFAULT_TUNNEL = "vxlan";
     private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
         {
@@ -116,11 +128,17 @@
     protected DeviceAdminService adminService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OvsdbController controller;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackSwitchingService openstackService;
+
     private final ExecutorService eventExecutor = Executors
             .newFixedThreadPool(NUM_THREADS, groupedThreads("onos/cordvtn", "event-handler"));
 
@@ -132,6 +150,8 @@
     private final VmHandler vmHandler = new VmHandler();
 
     private ConsistentMap<CordVtnNode, NodeState> nodeStore;
+    private Map<HostId, String> hostNetworkMap = Maps.newHashMap();
+    private CordVtnRuleInstaller ruleInstaller;
 
     private enum NodeState {
 
@@ -185,6 +205,8 @@
                 .withApplicationId(appId)
                 .build();
 
+        ruleInstaller = new CordVtnRuleInstaller(appId, flowObjectiveService,
+                                                 driverService, DEFAULT_TUNNEL);
         deviceService.addListener(deviceListener);
         hostService.addListener(hostListener);
 
@@ -314,11 +336,27 @@
 
     /**
      * Performs tasks after node initialization.
+     * First disconnect unnecessary OVSDB connection and then installs flow rules
+     * for existing VMs if there are any.
      *
      * @param node cordvtn node
      */
     private void postInit(CordVtnNode node) {
         disconnect(node);
+
+        Set<OpenstackNetwork> vNets = Sets.newHashSet();
+        hostService.getConnectedHosts(node.intBrId())
+                .stream()
+                .forEach(host -> {
+                    OpenstackNetwork vNet = getOpenstackNetworkByHost(host);
+                    if (vNet != null) {
+                        log.info("VM {} is detected", host.id());
+
+                        hostNetworkMap.put(host.id(), vNet.id());
+                        vNets.add(vNet);
+                    }
+                });
+        vNets.stream().forEach(this::installFlowRules);
     }
 
     /**
@@ -443,7 +481,7 @@
         }
 
         List<ControllerInfo> controllers = new ArrayList<>();
-        Sets.newHashSet(clusterService.getNodes())
+        Sets.newHashSet(clusterService.getNodes()).stream()
                 .forEach(controller -> {
                     ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
                     controllers.add(ctrlInfo);
@@ -453,7 +491,7 @@
         try {
             DriverHandler handler = driverService.createHandler(node.ovsdbId());
             BridgeConfig bridgeConfig =  handler.behaviour(BridgeConfig.class);
-            bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE_NAME), dpid, controllers);
+            bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
         } catch (ItemNotFoundException e) {
             log.warn("Failed to create integration bridge on {}", node.ovsdbId());
         }
@@ -474,13 +512,12 @@
             optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
         }
         TunnelDescription description =
-                new DefaultTunnelDescription(null, null, VXLAN,
-                                             TunnelName.tunnelName(DEFAULT_TUNNEL),
+                new DefaultTunnelDescription(null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
                                              optionBuilder.build());
         try {
             DriverHandler handler = driverService.createHandler(node.ovsdbId());
             TunnelConfig tunnelConfig =  handler.behaviour(TunnelConfig.class);
-            tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE_NAME), description);
+            tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
         } catch (ItemNotFoundException e) {
             log.warn("Failed to create tunnel interface on {}", node.ovsdbId());
         }
@@ -516,6 +553,212 @@
         }
     }
 
+    /**
+     * Returns tunnel port of the device.
+     *
+     * @param bridgeId device id
+     * @return port, null if no tunnel port exists on a given device
+     */
+    private Port getTunnelPort(DeviceId bridgeId) {
+        try {
+            return deviceService.getPorts(bridgeId).stream()
+                    .filter(p -> p.annotations().value("portName").contains(DEFAULT_TUNNEL)
+                            && p.isEnabled())
+                    .findFirst().get();
+        } catch (NoSuchElementException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns remote ip address for tunneling.
+     *
+     * @param bridgeId device id
+     * @return ip address, null if no such device exists
+     */
+    private IpAddress getRemoteIp(DeviceId bridgeId) {
+        CordVtnNode node = getNodeByBridgeId(bridgeId);
+        if (node != null) {
+            // TODO get data plane IP for tunneling
+            return node.ovsdbIp();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns destination information of all ports associated with a given
+     * OpenStack network. Output of the destination information is set to local
+     * port or tunnel port according to a given device id.
+     *
+     * @param deviceId device id to install flow rules
+     * @param vNet OpenStack network
+     * @return list of flow information, empty list if no flow information exists
+     */
+    private List<DestinationInfo> getSameNetworkPortsInfo(DeviceId deviceId, OpenstackNetwork vNet) {
+        List<DestinationInfo> dstInfos = Lists.newArrayList();
+        long tunnelId = Long.valueOf(vNet.segmentId());
+
+        for (OpenstackPort vPort : openstackService.ports(vNet.id())) {
+            ConnectPoint cp = getConnectPoint(vPort);
+            if (cp == null) {
+                log.debug("Couldn't find connection point for OpenStack port {}", vPort.id());
+                continue;
+            }
+
+            DestinationInfo.Builder dBuilder = cp.deviceId().equals(deviceId) ?
+                    DestinationInfo.builder(deviceService.getPort(cp.deviceId(), cp.port())) :
+                    DestinationInfo.builder(getTunnelPort(deviceId))
+                            .setRemoteIp(getRemoteIp(cp.deviceId()));
+
+            dBuilder.setMac(vPort.macAddress())
+                    .setTunnelId(tunnelId);
+            dstInfos.add(dBuilder.build());
+        }
+        return dstInfos;
+    }
+
+    /**
+     * Returns local ports associated with a given OpenStack network.
+     *
+     * @param bridgeId device id
+     * @param vNet OpenStack network
+     * @return port list, empty list if no port exists
+     */
+    private List<Port> getLocalSameNetworkPorts(DeviceId bridgeId, OpenstackNetwork vNet) {
+        List<Port> ports = new ArrayList<>();
+        openstackService.ports(vNet.id()).stream().forEach(port -> {
+            ConnectPoint cp = getConnectPoint(port);
+            if (cp != null && cp.deviceId().equals(bridgeId)) {
+                ports.add(deviceService.getPort(cp.deviceId(), cp.port()));
+            }
+        });
+        return ports;
+    }
+
+    /**
+     * Returns OpenStack port associated with a given host.
+     *
+     * @param host host
+     * @return OpenStack port, or null if no port has been found
+     */
+    private OpenstackPort getOpenstackPortByHost(Host host) {
+        Port port = deviceService.getPort(host.location().deviceId(),
+                                          host.location().port());
+        return openstackService.port(port);
+    }
+
+    /**
+     * Returns OpenStack network associated with a given host.
+     *
+     * @param host host
+     * @return OpenStack network, or null if no network has been found
+     */
+    private OpenstackNetwork getOpenstackNetworkByHost(Host host) {
+        OpenstackPort vPort = getOpenstackPortByHost(host);
+        if (vPort != null) {
+            return openstackService.network(vPort.networkId());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns port name with OpenStack port information.
+     *
+     * @param vPort OpenStack port
+     * @return port name
+     */
+    private String getPortName(OpenstackPort vPort) {
+        checkNotNull(vPort);
+        return VPORT_PREFIX + vPort.id().substring(0, 10);
+    }
+
+    /**
+     * Returns connect point of a given OpenStack port.
+     * It assumes there's only one physical port associated with an OpenStack port.
+     *
+     * @param vPort openstack port
+     * @return connect point, null if no such port exists
+     */
+    private ConnectPoint getConnectPoint(OpenstackPort vPort) {
+        try {
+            Host host = hostService.getHostsByMac(vPort.macAddress())
+                    .stream()
+                    .findFirst()
+                    .get();
+            return new ConnectPoint(host.location().deviceId(), host.location().port());
+        } catch (NoSuchElementException e) {
+            log.debug("Not a valid host with {}", vPort.macAddress());
+            return null;
+        }
+    }
+
+    /**
+     * Installs flow rules for a given OpenStack network.
+     *
+     * @param vNet OpenStack network
+     */
+    private void installFlowRules(OpenstackNetwork vNet) {
+        checkNotNull(vNet, "Tenant network should not be null");
+
+        for (Device device : deviceService.getAvailableDevices(SWITCH)) {
+            List<DestinationInfo> dstInfos = getSameNetworkPortsInfo(device.id(), vNet);
+
+            for (Port inPort : getLocalSameNetworkPorts(device.id(), vNet)) {
+                List<DestinationInfo> localInInfos = dstInfos.stream()
+                        .filter(info -> !info.output().equals(inPort))
+                        .collect(Collectors.toList());
+                ruleInstaller.installFlowRulesLocalIn(device.id(), inPort, localInInfos);
+            }
+
+            Port tunPort = getTunnelPort(device.id());
+            List<DestinationInfo> tunnelInInfos = dstInfos.stream()
+                    .filter(info -> !info.output().equals(tunPort))
+                    .collect(Collectors.toList());
+            ruleInstaller.installFlowRulesTunnelIn(device.id(), tunPort, tunnelInInfos);
+        }
+    }
+
+    /**
+     * Uninstalls flow rules associated with a given host for a given OpenStack network.
+     *
+     * @param vNet OpenStack network
+     * @param host removed host
+     */
+    private void uninstallFlowRules(OpenstackNetwork vNet, Host host) {
+        checkNotNull(vNet, "Tenant network should not be null");
+
+        Port removedPort = deviceService.getPort(host.location().deviceId(),
+                                                 host.location().port());
+
+        for (Device device : deviceService.getAvailableDevices(SWITCH)) {
+            List<DestinationInfo> dstInfos = getSameNetworkPortsInfo(device.id(), vNet);
+
+            for (Port inPort : getLocalSameNetworkPorts(device.id(), vNet)) {
+                List<DestinationInfo> localInInfos = Lists.newArrayList(
+                        DestinationInfo.builder(getTunnelPort(device.id()))
+                                .setTunnelId(Long.valueOf(vNet.segmentId()))
+                                .setMac(host.mac())
+                                .setRemoteIp(getRemoteIp(host.location().deviceId()))
+                                .build());
+                ruleInstaller.uninstallFlowRules(device.id(), inPort, localInInfos);
+            }
+
+            if (device.id().equals(host.location().deviceId())) {
+                Port tunPort = getTunnelPort(device.id());
+                List<DestinationInfo> tunnelInInfo = Lists.newArrayList(
+                        DestinationInfo.builder(removedPort)
+                                .setTunnelId(Long.valueOf(vNet.segmentId()))
+                                .setMac(host.mac())
+                                .build());
+
+                ruleInstaller.uninstallFlowRules(device.id(), tunPort, tunnelInInfo);
+                ruleInstaller.uninstallFlowRules(device.id(), removedPort, dstInfos);
+            }
+        }
+    }
+
     private class InternalDeviceListener implements DeviceListener {
 
         @Override
@@ -644,12 +887,40 @@
 
         @Override
         public void connected(Host host) {
+            CordVtnNode node = getNodeByBridgeId(host.location().deviceId());
+            if (node == null || !getNodeState(node).equals(NodeState.COMPLETE)) {
+                // do nothing for the host on unregistered or unprepared device
+                return;
+            }
+
+            OpenstackNetwork vNet = getOpenstackNetworkByHost(host);
+            if (vNet == null) {
+                return;
+            }
+
             log.info("VM {} is detected", host.id());
+
+            hostNetworkMap.put(host.id(), vNet.id());
+            installFlowRules(vNet);
         }
 
         @Override
         public void disconnected(Host host) {
+            CordVtnNode node = getNodeByBridgeId(host.location().deviceId());
+            if (node == null || !getNodeState(node).equals(NodeState.COMPLETE)) {
+                // do nothing for the host on unregistered or unprepared device
+                return;
+            }
+
+            OpenstackNetwork vNet = openstackService.network(hostNetworkMap.get(host.id()));
+            if (vNet == null) {
+                return;
+            }
+
             log.info("VM {} is vanished", host.id());
+
+            uninstallFlowRules(vNet, host);
+            hostNetworkMap.remove(host.id());
         }
     }
 }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
new file mode 100644
index 0000000..9e22997
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2015 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;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+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.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.slf4j.Logger;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+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;
+
+/**
+ * Populates rules for virtual tenant network.
+ */
+public final class CordVtnRuleInstaller {
+    protected final Logger log = getLogger(getClass());
+
+    private static final int DEFAULT_PRIORITY = 5000;
+
+    private final ApplicationId appId;
+    private final FlowObjectiveService flowObjectiveService;
+    private final DriverService driverService;
+    private final String tunnelType;
+
+    /**
+     * Creates a new rule installer.
+     *
+     * @param appId application id
+     * @param flowObjectiveService flow objective service
+     * @param driverService driver service
+     * @param tunnelType tunnel type
+     */
+    public CordVtnRuleInstaller(ApplicationId appId,
+                                FlowObjectiveService flowObjectiveService,
+                                DriverService driverService,
+                                String tunnelType) {
+        this.appId = appId;
+        this.flowObjectiveService = flowObjectiveService;
+        this.driverService = driverService;
+        this.tunnelType = checkNotNull(tunnelType);
+    }
+
+    /**
+     * Installs flow rules for tunnel in traffic.
+     *
+     * @param deviceId device id to install flow rules
+     * @param inPort in port
+     * @param dstInfos list of destination info
+     */
+    public void installFlowRulesTunnelIn(DeviceId deviceId, Port inPort, List<DestinationInfo> dstInfos) {
+        dstInfos.stream().forEach(dstInfo -> {
+            ForwardingObjective.Builder fBuilder = vtnRulesSameNode(inPort, dstInfo);
+            if (fBuilder != null) {
+                flowObjectiveService.forward(deviceId, fBuilder.add());
+            }
+        });
+    }
+
+    /**
+     * Installs flow rules for local in traffic.
+     *
+     * @param deviceId device id to install flow rules
+     * @param inPort in port
+     * @param dstInfos list of destination info
+     */
+    public void installFlowRulesLocalIn(DeviceId deviceId, Port inPort, List<DestinationInfo> dstInfos) {
+        dstInfos.stream().forEach(dstInfo -> {
+            ForwardingObjective.Builder fBuilder = isTunnelPort(dstInfo.output()) ?
+                    vtnRulesRemoteNode(deviceId, inPort, dstInfo) : vtnRulesSameNode(inPort, dstInfo);
+
+            if (fBuilder != null) {
+                flowObjectiveService.forward(deviceId, fBuilder.add());
+            }
+        });
+    }
+
+    /**
+     * Uninstalls flow rules associated with a given port from a given device.
+     *
+     * @param deviceId device id
+     * @param inPort port associated with removed host
+     * @param dstInfos list of destination info
+     */
+    public void uninstallFlowRules(DeviceId deviceId, Port inPort, List<DestinationInfo> dstInfos) {
+        dstInfos.stream().forEach(dstInfo -> {
+            ForwardingObjective.Builder fBuilder = isTunnelPort(dstInfo.output()) ?
+                    vtnRulesRemoteNode(deviceId, inPort, dstInfo) : vtnRulesSameNode(inPort, dstInfo);
+
+            if (fBuilder != null) {
+                flowObjectiveService.forward(deviceId, fBuilder.remove());
+            }
+        });
+    }
+
+    /**
+     * Returns forwarding objective builder to provision basic virtual tenant network.
+     * This method cares for the traffics whose source and destination device is the same.
+     *
+     * @param inPort in port
+     * @param dstInfo destination information
+     * @return forwarding objective builder
+     */
+    private ForwardingObjective.Builder vtnRulesSameNode(Port inPort, DestinationInfo dstInfo) {
+        checkArgument(inPort.element().id().equals(dstInfo.output().element().id()));
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchInPort(inPort.number())
+                .matchEthDst(dstInfo.mac());
+        if (isTunnelPort(inPort)) {
+            sBuilder.matchTunnelId(dstInfo.tunnelId());
+        }
+
+        tBuilder.setOutput(dstInfo.output().number());
+
+        return DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(tBuilder.build())
+                .withPriority(DEFAULT_PRIORITY)
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .fromApp(appId)
+                .makePermanent();
+    }
+
+    /**
+     * Returns forwarding objective builder to provision basic virtual tenant network.
+     * This method cares for the traffics whose source and destination is not the same.
+     *
+     * @param deviceId device id to install flow rules
+     * @param inPort in port
+     * @param dstInfo destination information
+     * @return forwarding objective, or null if it fails to build it
+     */
+    private ForwardingObjective.Builder vtnRulesRemoteNode(DeviceId deviceId, Port inPort, DestinationInfo dstInfo) {
+        checkArgument(isTunnelPort(dstInfo.output()));
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        ExtensionTreatment extTreatment =
+                getTunnelDstInstruction(deviceId, dstInfo.remoteIp().getIp4Address());
+        if (extTreatment == null) {
+            return null;
+        }
+
+        sBuilder.matchInPort(inPort.number())
+                .matchEthDst(dstInfo.mac());
+
+        tBuilder.extension(extTreatment, deviceId)
+                .setTunnelId(dstInfo.tunnelId())
+                .setOutput(dstInfo.output().number());
+
+        return DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(tBuilder.build())
+                .withPriority(DEFAULT_PRIORITY)
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .fromApp(appId)
+                .makePermanent();
+    }
+
+    /**
+     * Checks if a given port is tunnel interface or not.
+     * It assumes the tunnel interface contains tunnelType string in its name.
+     *
+     * @param port port
+     * @return true if the port is tunnel interface, false otherwise.
+     */
+    private boolean isTunnelPort(Port port) {
+        return port.annotations().value("portName").contains(tunnelType);
+    }
+
+    /**
+     * 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 getTunnelDstInstruction(DeviceId deviceId, Ip4Address remoteIp) {
+        try {
+            Driver driver = driverService.getDriver(deviceId);
+            DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, deviceId));
+            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 to set tunnel dst {}", deviceId);
+            return null;
+        }
+    }
+}
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java
new file mode 100644
index 0000000..290cc17
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014-2015 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;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.Port;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Contains destination information.
+ */
+public final class DestinationInfo {
+
+    private final Port output;
+    private final List<IpAddress> ip;
+    private final MacAddress mac;
+    private final IpAddress remoteIp;
+    private final long tunnelId;
+
+    /**
+     * Creates a new destination information.
+     *
+     * @param output output port
+     * @param ip destination ip address
+     * @param mac destination mac address
+     * @param remoteIp tunnel remote ip address
+     * @param tunnelId segment id
+     */
+    public DestinationInfo(Port output, List<IpAddress> ip, MacAddress mac,
+                           IpAddress remoteIp, long tunnelId) {
+        this.output = checkNotNull(output);
+        this.ip = ip;
+        this.mac = mac;
+        this.remoteIp = remoteIp;
+        this.tunnelId = tunnelId;
+    }
+
+    /**
+     * Returns output port.
+     *
+     * @return port
+     */
+    public Port output() {
+        return output;
+    }
+
+    /**
+     * Returns destination ip addresses.
+     *
+     * @return list of ip address
+     */
+    public List<IpAddress> ip() {
+        return ip;
+    }
+
+    /**
+     * Returns destination mac address.
+     *
+     * @return mac address
+     */
+    public MacAddress mac() {
+        return mac;
+    }
+
+    /**
+     * Returns tunnel remote ip address.
+     *
+     * @return ip address
+     */
+    public IpAddress remoteIp() {
+        return remoteIp;
+    }
+
+    /**
+     * Returns tunnel id.
+     *
+     * @return tunnel id
+     */
+    public long tunnelId() {
+        return tunnelId;
+    }
+
+    /**
+     * Returns a new destination info builder.
+     *
+     * @return destination info builder
+     */
+    public static DestinationInfo.Builder builder(Port output) {
+        return new Builder(output);
+    }
+
+    /**
+     * DestinationInfo builder class.
+     */
+    public static final class Builder {
+
+        private final Port output;
+        private List<IpAddress> ip;
+        private MacAddress mac;
+        private IpAddress remoteIp;
+        private long tunnelId;
+
+        /**
+         * Creates a new destination information builder.
+         *
+         * @param output output port
+         */
+        public Builder(Port output) {
+            this.output = checkNotNull(output, "Output port cannot be null");
+        }
+
+        /**
+         * Sets the destination ip address.
+         *
+         * @param ip ip address
+         * @return destination info builder
+         */
+        public Builder setIp(List<IpAddress> ip) {
+            this.ip = checkNotNull(ip, "IP cannot be null");
+            return this;
+        }
+
+        /**
+         * Sets the destination mac address.
+         *
+         * @param mac mac address
+         * @return destination info builder
+         */
+        public Builder setMac(MacAddress mac) {
+            this.mac = checkNotNull(mac, "MAC address cannot be null");
+            return this;
+        }
+
+        /**
+         * Sets the tunnel remote ip address.
+         *
+         * @param remoteIp ip address
+         * @return destination info builder
+         */
+        public Builder setRemoteIp(IpAddress remoteIp) {
+            this.remoteIp = checkNotNull(remoteIp, "Remote IP address cannot be null");
+            return this;
+        }
+
+        /**
+         * Sets the tunnel id.
+         *
+         * @param tunnelId tunnel id
+         * @return destination info builder
+         */
+        public Builder setTunnelId(long tunnelId) {
+            this.tunnelId = checkNotNull(tunnelId, "Tunnel ID cannot be null");
+            return this;
+        }
+
+        /**
+         * Build a destination information.
+         *
+         * @return destination info object
+         */
+        public DestinationInfo build() {
+            return new DestinationInfo(this);
+        }
+    }
+
+    private DestinationInfo(Builder builder) {
+        output = builder.output;
+        ip = builder.ip;
+        mac = builder.mac;
+        remoteIp = builder.remoteIp;
+        tunnelId = builder.tunnelId;
+    }
+}