[ONOS-6249] (vNet) Link mapping and end-to-end isolation for virtual network

A virtual tenant's topology graph should not be restricted to the substrate's subgraph.
Thus, we need to set up a tunnel on the physical path through which a virtual link passes.
Furthermore, isolating the traffic from different virtual networks is also needed.

Change-Id: Ia5dc49d34ec96d3a7b9c55ff7a6df3b10ad93697
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
index a9bff1d..ef8fa8c 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
@@ -34,6 +34,7 @@
 import org.onosproject.incubator.net.virtual.NetworkId;
 import org.onosproject.incubator.net.virtual.VirtualNetworkService;
 import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.VirtualLink;
 import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
 import org.onosproject.incubator.net.virtual.provider.InternalRoutingAlgorithm;
 import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
@@ -73,6 +74,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -392,15 +394,62 @@
         if (ingressPoint.deviceId().equals(egressPoint.deviceId()) ||
                 egressPoint.port().isLogical()) {
             //Traffic is handled inside a single physical switch
-            //No tunnel is needed.
 
             TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector
                     .builder(commonSelector)
                     .matchInPort(ingressPoint.port());
 
             TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment
-                    .builder(commonTreatment)
-                    .setOutput(egressPoint.port());
+                    .builder(commonTreatment);
+
+            VirtualPort virtualIngressPort = vnService
+                    .getVirtualPorts(networkId, flowRule.deviceId())
+                    .stream()
+                    .filter(p -> p.realizedBy().equals(ingressPoint))
+                    .findFirst()
+                    .get();
+
+            VirtualPort virtualEgressPort = vnService
+                    .getVirtualPorts(networkId, flowRule.deviceId())
+                    .stream()
+                    .filter(p -> p.realizedBy().equals(egressPoint))
+                    .findFirst()
+                    .get();
+
+            ConnectPoint ingressCp = new ConnectPoint(virtualIngressPort.element().id(), virtualIngressPort.number());
+            ConnectPoint egressCp = new ConnectPoint(virtualEgressPort.element().id(), virtualEgressPort.number());
+
+            Optional<VirtualLink> optionalIngressLink = vnService
+                    .getVirtualLinks(networkId)
+                    .stream()
+                    .filter(l -> l.dst().equals(ingressCp))
+                    .findFirst();
+
+            Optional<VirtualLink> optionalEgressLink = vnService
+                    .getVirtualLinks(networkId)
+                    .stream()
+                    .filter(l -> l.src().equals(egressCp))
+                    .findFirst();
+
+            //Isolate traffic from different virtual networks with VLAN
+            if (!optionalIngressLink.isPresent() && !optionalEgressLink.isPresent()) {
+                treatmentBuilder.setOutput(egressPoint.port());
+            } else if (optionalIngressLink.isPresent() && !optionalEgressLink.isPresent()) {
+                selectorBuilder.matchVlanId(VlanId.vlanId(networkId.id().shortValue()));
+                treatmentBuilder.popVlan();
+                treatmentBuilder.setOutput(egressPoint.port());
+            } else if (!optionalIngressLink.isPresent() && optionalEgressLink.isPresent()) {
+                outRules.addAll(generateRulesOnPath(networkId, optionalEgressLink.get(),
+                        commonSelector, commonTreatment, flowRule));
+                treatmentBuilder.pushVlan()
+                        .setVlanId(VlanId.vlanId(networkId.id().shortValue()));
+                treatmentBuilder.setOutput(egressPoint.port());
+            } else if (optionalIngressLink.isPresent() && optionalEgressLink.isPresent()) {
+                outRules.addAll(generateRulesOnPath(networkId, optionalEgressLink.get(),
+                        commonSelector, commonTreatment, flowRule));
+                selectorBuilder.matchVlanId(VlanId.vlanId(networkId.id().shortValue()));
+                treatmentBuilder.setOutput(egressPoint.port());
+            }
 
             FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
                     .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
@@ -522,6 +571,76 @@
         return outRules;
     }
 
+    /**
+     * Generate flow rules to the intermediate nodes on the physical path for a virtual link.
+     *
+     * @param networkId The virtual network identifier
+     * @param virtualLink A virtual link
+     * @param commonSelector A common traffic selector between the virtual
+     *                       and physical flow rules
+     * @param commonTreatment A common traffic treatment between the virtual
+     *                        and physical flow rules
+     * @param flowRule The virtual flow rule to be translated
+     * @return A set of flow rules for the path on physical network
+     */
+    private Set<FlowRule> generateRulesOnPath(NetworkId networkId,
+                                              VirtualLink virtualLink,
+                                              TrafficSelector commonSelector,
+                                              TrafficTreatment commonTreatment,
+                                              FlowRule flowRule) {
+
+        VirtualPort srcVirtualPort = vnService
+                .getVirtualPorts(networkId, virtualLink.src().deviceId())
+                .stream()
+                .filter(p -> p.number().equals(virtualLink.src().port()))
+                .findFirst()
+                .get();
+
+        VirtualPort dstVirtualPort = vnService
+                .getVirtualPorts(networkId, virtualLink.dst().deviceId())
+                .stream()
+                .filter(p -> p.number().equals(virtualLink.dst().port()))
+                .findFirst()
+                .get();
+        Set<FlowRule> outRules = new HashSet<>();
+        ConnectPoint srcCp = srcVirtualPort.realizedBy();
+        ConnectPoint dstCp = dstVirtualPort.realizedBy();
+
+        Path internalPath = internalRoutingAlgorithm
+                .findPath(srcCp, dstCp);
+        List<Link> links = internalPath.links();
+        if (internalPath != null && links.size() > 1) {
+            for (int i = 0; i < links.size() - 1; i++) {
+                ConnectPoint inCp = links.get(i).dst();
+                ConnectPoint outCp = links.get(i + 1).src();
+                TrafficSelector.Builder linkSelectorBuilder = DefaultTrafficSelector
+                        .builder(commonSelector)
+                        .matchVlanId(VlanId.vlanId(networkId.id().shortValue()))
+                        .matchInPort(inCp.port());
+
+                TrafficTreatment.Builder linkTreatmentBuilder = DefaultTrafficTreatment
+                        .builder(commonTreatment)
+                        .setOutput(outCp.port());
+
+                FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                        .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+                        .forDevice(inCp.deviceId())
+                        .withSelector(linkSelectorBuilder.build())
+                        .withTreatment(linkTreatmentBuilder.build())
+                        .withPriority(flowRule.priority());
+
+                if (flowRule.isPermanent()) {
+                    ruleBuilder.makePermanent();
+                } else {
+                    ruleBuilder.makeTemporary(flowRule.timeout());
+                }
+
+                outRules.add(ruleBuilder.build());
+            }
+        }
+        return outRules;
+    }
+
     private class InternalFlowRuleListener implements FlowRuleListener {
         @Override
         public void event(FlowRuleEvent event) {
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java
index 2b010bb..e268f32 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java
@@ -347,7 +347,7 @@
 
         @Override
         public Set<VirtualLink> getVirtualLinks(NetworkId networkId) {
-            return null;
+            return new HashSet<>();
         }
 
         @Override
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualNetworkStore.java b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualNetworkStore.java
index 4b79cf5..81a9a3c 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualNetworkStore.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/DistributedVirtualNetworkStore.java
@@ -514,8 +514,14 @@
         if (virtualLinkSet == null) {
             virtualLinkSet = new HashSet<>();
         }
+
         // validate that the link does not already exist in this network
-        checkState(getLink(networkId, src, dst) == null, "The virtual link already exists");
+        checkState(getLink(networkId, src, dst) == null,
+                "The virtual link already exists");
+        checkState(getLink(networkId, src, null) == null,
+                "The source connection point has been used by another link");
+        checkState(getLink(networkId, null, dst) == null,
+                "The destination connection point has been used by another link");
 
         VirtualLink virtualLink = DefaultVirtualLink.builder()
                 .networkId(networkId)
@@ -536,8 +542,14 @@
         Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(virtualLink.networkId());
         if (virtualLinkSet == null) {
             virtualLinkSet = new HashSet<>();
+            networkIdVirtualLinkSetMap.put(virtualLink.networkId(), virtualLinkSet);
+            log.warn("The updated virtual link {} has not been added", virtualLink);
+            return;
         }
-        virtualLinkSet.remove(virtualLink);
+        if (!virtualLinkSet.remove(virtualLink)) {
+            log.warn("The updated virtual link {} does not exist", virtualLink);
+            return;
+        }
 
         VirtualLink newVirtualLink = DefaultVirtualLink.builder()
                 .networkId(virtualLink.networkId())
@@ -557,6 +569,7 @@
 
         final VirtualLink virtualLink = getLink(networkId, src, dst);
         if (virtualLink == null) {
+            log.warn("The removed virtual link between {} and {} does not exist", src, dst);
             return null;
         }
         Set<VirtualLink> virtualLinkSet = new HashSet<>();
@@ -742,7 +755,13 @@
 
         VirtualLink virtualLink = null;
         for (VirtualLink link : virtualLinkSet) {
-            if (link.src().equals(src) && link.dst().equals(dst)) {
+            if (src == null && link.dst().equals(dst)) {
+                virtualLink = link;
+                break;
+            } else if (dst == null && link.src().equals(src)) {
+                virtualLink = link;
+                break;
+            } else if (link.src().equals(src) && link.dst().equals(dst)) {
                 virtualLink = link;
                 break;
             }