ONOS-3633 - Implementation of virtual network point to point intent provider.

Change-Id: Ie2c1e5ac278bc0dd6259479c44dd92b9b625e90b
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/PtToPtIntentVirtualNetworkProvider.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/PtToPtIntentVirtualNetworkProvider.java
new file mode 100644
index 0000000..d879f59
--- /dev/null
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/PtToPtIntentVirtualNetworkProvider.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.VirtualNetworkProviderService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.net.provider.AbstractProvider;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.Thread.sleep;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Point to point intent VirtualNetworkProvider implementation.
+ */
+@Component(immediate = true)
+@Service
+public class PtToPtIntentVirtualNetworkProvider extends AbstractProvider implements VirtualNetworkProvider {
+
+    private final Logger log = getLogger(PtToPtIntentVirtualNetworkProvider.class);
+    private static final String NETWORK_ID_NULL = "Network ID cannot be null";
+    private static final String CONNECT_POINT_NULL = "Connect Point cannot be null";
+    private static final String INTENT_NULL = "Intent cannot be null";
+    protected static final String KEY_FORMAT = "networkId=%s src=%s dst=%s";
+    private static final int MAX_WAIT_COUNT = 30;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VirtualNetworkProviderRegistry providerRegistry;
+
+    private VirtualNetworkProviderService providerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    protected static final String PTPT_INTENT_APPID = "org.onosproject.vnet.intent";
+    private ApplicationId appId;
+
+    /**
+     * Default constructor.
+     */
+    public PtToPtIntentVirtualNetworkProvider() {
+        super(DefaultVirtualLink.PID);
+    }
+
+    @Activate
+    public void activate() {
+        providerService = providerRegistry.register(this);
+        appId = coreService.registerApplication(PTPT_INTENT_APPID);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        providerRegistry.unregister(this);
+        providerService = null;
+        log.info("Stopped");
+    }
+
+    @Override
+    public TunnelId createTunnel(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+        checkNotNull(NETWORK_ID_NULL, networkId);
+        checkNotNull(CONNECT_POINT_NULL, src);
+        checkNotNull(CONNECT_POINT_NULL, dst);
+        String key = String.format(KEY_FORMAT, networkId.toString(), src.toString(), dst.toString());
+        Key intentKey = Key.of(key, appId);
+
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new EncapsulationConstraint(EncapsulationType.VLAN));
+
+        // TODO Currently there can only be one tunnel/intent between the src and dst across
+        // all virtual networks. We may want to support multiple intents between the same src/dst pairs.
+        PointToPointIntent intent = PointToPointIntent.builder()
+                .key(intentKey)
+                .appId(appId)
+                .ingressPoint(src)
+                .egressPoint(dst)
+                .constraints(constraints)
+                .build();
+        intentService.submit(intent);
+
+        // construct tunnelId from the key
+        return TunnelId.valueOf(key);
+    }
+
+    @Override
+    public void destroyTunnel(NetworkId networkId, TunnelId tunnelId) {
+        String key = tunnelId.id();
+        Key intentKey = Key.of(key, appId);
+        Intent intent = intentService.getIntent(intentKey);
+        checkNotNull(intent, INTENT_NULL);
+        intentService.withdraw(intent);
+        try {
+            int count = 0;
+            // Loop waiting for the intent to go into a withdrawn or failed state
+            // before attempting to purge it.
+            while (++count <= MAX_WAIT_COUNT) {
+                IntentState state = intentService.getIntentState(intentKey);
+                if ((state == IntentState.FAILED) || (state == IntentState.WITHDRAWN)) {
+                    intentService.purge(intent);
+                    break;
+                }
+                sleep(1000);
+            }
+        } catch (Exception e) {
+            log.error("Exception: " + e);
+        }
+    }
+}
+
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
index fdeed25..b1b68a1 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
@@ -66,6 +66,7 @@
     private static final String NETWORK_NULL = "Network ID cannot be null";
     private static final String DEVICE_NULL = "Device ID cannot be null";
     private static final String LINK_POINT_NULL = "Link end-point cannot be null";
+    private static final String VIRTUAL_LINK_NULL = "Virtual Link cannot be null";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected VirtualNetworkStore store;
@@ -135,13 +136,41 @@
 
     @Override
     public VirtualLink createVirtualLink(NetworkId networkId,
-                                         ConnectPoint src, ConnectPoint dst,
-                                         TunnelId realizedBy) {
+                                         ConnectPoint src, ConnectPoint dst) {
         checkNotNull(networkId, NETWORK_NULL);
         checkNotNull(src, LINK_POINT_NULL);
         checkNotNull(dst, LINK_POINT_NULL);
-        checkNotNull(realizedBy, "Tunnel ID cannot be null");
-        return store.addLink(networkId, src, dst, realizedBy);
+        VirtualLink virtualLink = store.addLink(networkId, src, dst, null);
+        checkNotNull(virtualLink, VIRTUAL_LINK_NULL);
+
+        if (virtualLink.providerId() != null) {
+            VirtualNetworkProvider provider = getProvider(virtualLink.providerId());
+            if (provider != null) {
+                TunnelId tunnelId = provider.createTunnel(networkId, mapVirtualToPhysicalPort(networkId, src),
+                                                          mapVirtualToPhysicalPort(networkId, dst));
+                store.updateLink(virtualLink, tunnelId);
+            }
+        }
+        return virtualLink;
+    }
+
+    /**
+     * Maps the virtual connect point to a physical connect point.
+     *
+     * @param networkId network identifier
+     * @param virtualCp virtual connect point
+     * @return physical connect point
+     */
+    private ConnectPoint mapVirtualToPhysicalPort(NetworkId networkId,
+                                                  ConnectPoint virtualCp) {
+        Set<VirtualPort> ports = store.getPorts(networkId, virtualCp.deviceId());
+        for (VirtualPort port : ports) {
+            if (port.element().id().equals(virtualCp.elementId()) &&
+                    port.number().equals(virtualCp.port())) {
+                return new ConnectPoint(port.realizedBy().element().id(), port.realizedBy().number());
+            }
+        }
+        return null;
     }
 
     @Override
@@ -149,7 +178,14 @@
         checkNotNull(networkId, NETWORK_NULL);
         checkNotNull(src, LINK_POINT_NULL);
         checkNotNull(dst, LINK_POINT_NULL);
-        store.removeLink(networkId, src, dst);
+        VirtualLink virtualLink = store.removeLink(networkId, src, dst);
+
+        if (virtualLink != null && virtualLink.providerId() != null) {
+            VirtualNetworkProvider provider = getProvider(virtualLink.providerId());
+            if (provider != null) {
+                provider.destroyTunnel(networkId, virtualLink.tunnelId());
+            }
+        }
     }
 
     @Override
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/PtToPtIntentVirtualNetworkProviderTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/PtToPtIntentVirtualNetworkProviderTest.java
new file mode 100644
index 0000000..2f2926b
--- /dev/null
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/PtToPtIntentVirtualNetworkProviderTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Sets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.VirtualNetworkProviderService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentServiceAdapter;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Junit tests for PtToPtIntentVirtualNetworkProvider.
+ */
+public class PtToPtIntentVirtualNetworkProviderTest {
+
+    private PtToPtIntentVirtualNetworkProvider provider;
+    private VirtualNetworkProviderRegistry providerRegistry;
+
+    private final VirtualNetworkRegistryAdapter virtualNetworkRegistry = new VirtualNetworkRegistryAdapter();
+    private IntentService intentService;
+
+    private static final ApplicationId APP_ID =
+            TestApplicationId.create(PtToPtIntentVirtualNetworkProvider.PTPT_INTENT_APPID);
+
+    private IdGenerator idGenerator = new MockIdGenerator();
+
+    @Before
+    public void setUp() {
+        provider = new PtToPtIntentVirtualNetworkProvider();
+        provider.providerRegistry = virtualNetworkRegistry;
+        final CoreService mockCoreService = createMock(CoreService.class);
+        provider.coreService = mockCoreService;
+        expect(mockCoreService.registerApplication(PtToPtIntentVirtualNetworkProvider.PTPT_INTENT_APPID))
+                .andReturn(APP_ID).anyTimes();
+        replay(mockCoreService);
+        Intent.unbindIdGenerator(idGenerator);
+        Intent.bindIdGenerator(idGenerator);
+
+        intentService = new TestIntentService();
+        provider.intentService = intentService;
+        provider.activate();
+    }
+
+    @After
+    public void tearDown() {
+        provider.deactivate();
+        provider.providerRegistry = null;
+        provider.coreService = null;
+        provider.intentService = null;
+        Intent.unbindIdGenerator(idGenerator);
+    }
+
+    @Test
+    public void basics() {
+        assertNotNull("registration expected", provider);
+    }
+
+    /**
+     * Test a null network identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateTunnelNullNetworkId() {
+        provider.createTunnel(null, null, null);
+    }
+
+    /**
+     * Test a null source connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateTunnelNullSrc() {
+        ConnectPoint dst = new ConnectPoint(DeviceId.deviceId("device2"), PortNumber.portNumber(2));
+
+        provider.createTunnel(NetworkId.networkId(0), null, dst);
+    }
+
+    /**
+     * Test a null destination connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateTunnelNullDst() {
+        ConnectPoint src = new ConnectPoint(DeviceId.deviceId("device1"), PortNumber.portNumber(1));
+
+        provider.createTunnel(NetworkId.networkId(0), src, null);
+    }
+
+    /**
+     * Test creating/destroying a valid tunnel.
+     */
+    @Test
+    public void testCreateRemoveTunnel() {
+        NetworkId networkId = NetworkId.networkId(0);
+        ConnectPoint src = new ConnectPoint(DeviceId.deviceId("device1"), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(DeviceId.deviceId("device2"), PortNumber.portNumber(2));
+
+        TunnelId tunnelId = provider.createTunnel(networkId, src, dst);
+        String key = String.format(PtToPtIntentVirtualNetworkProvider.KEY_FORMAT,
+                                   networkId.toString(), src.toString(), dst.toString());
+
+        assertEquals("TunnelId does not match as expected.", key, tunnelId.toString());
+        provider.destroyTunnel(networkId, tunnelId);
+    }
+
+    /**
+     * Virtual network registry implementation for this test class.
+     */
+    private class VirtualNetworkRegistryAdapter implements VirtualNetworkProviderRegistry {
+        private VirtualNetworkProvider provider;
+
+        @Override
+        public VirtualNetworkProviderService register(VirtualNetworkProvider theProvider) {
+            this.provider = theProvider;
+            return new TestVirtualNetworkProviderService(theProvider);
+        }
+
+        @Override
+        public void unregister(VirtualNetworkProvider theProvider) {
+            this.provider = null;
+        }
+
+        @Override
+        public Set<ProviderId> getProviders() {
+            return null;
+        }
+    }
+
+    /**
+     * Virtual network provider service implementation for this test class.
+     */
+    private class TestVirtualNetworkProviderService
+            extends AbstractProviderService<VirtualNetworkProvider>
+            implements VirtualNetworkProviderService {
+
+        protected TestVirtualNetworkProviderService(VirtualNetworkProvider provider) {
+            super(provider);
+        }
+    }
+
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+    }
+
+    /**
+     * Represents a fake IntentService class that easily allows to store and
+     * retrieve intents without implementing the IntentService logic.
+     */
+    private class TestIntentService extends IntentServiceAdapter {
+
+        private Set<Intent> intents;
+
+        public TestIntentService() {
+            intents = Sets.newHashSet();
+        }
+
+        @Override
+        public void submit(Intent intent) {
+            intents.add(intent);
+        }
+
+        @Override
+        public void withdraw(Intent intent) {
+        }
+
+        @Override
+        public IntentState getIntentState(Key intentKey) {
+            return IntentState.WITHDRAWN;
+        }
+
+        @Override
+        public void purge(Intent intent) {
+            intents.remove(intent);
+        }
+
+        @Override
+        public long getIntentCount() {
+            return intents.size();
+        }
+
+        @Override
+        public Iterable<Intent> getIntents() {
+            return intents;
+        }
+
+        @Override
+        public Intent getIntent(Key intentKey) {
+            for (Intent intent : intents) {
+                if (intent.key().equals(intentKey)) {
+                    return intent;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java
index 214e252..837b501 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java
@@ -27,7 +27,6 @@
 import org.onosproject.core.CoreServiceAdapter;
 import org.onosproject.core.IdGenerator;
 import org.onosproject.event.Event;
-import org.onosproject.incubator.net.tunnel.TunnelId;
 import org.onosproject.incubator.net.virtual.DefaultVirtualNetwork;
 import org.onosproject.incubator.net.virtual.NetworkId;
 import org.onosproject.incubator.net.virtual.TenantId;
@@ -247,8 +246,8 @@
                 manager.createVirtualDevice(virtualNetwork1.id(), DeviceId.deviceId(deviceIdValue2));
         ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
         ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
-        manager.createVirtualLink(virtualNetwork1.id(), src, dst, TunnelId.valueOf(0));
-        manager.createVirtualLink(virtualNetwork1.id(), dst, src, TunnelId.valueOf(1));
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork1.id(), dst, src);
 
         Set<VirtualLink> virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
         assertNotNull("The virtual link set should not be null", virtualLinks);
@@ -263,13 +262,30 @@
         assertTrue("The virtual link set should be empty.", virtualLinks.isEmpty());
 
         // Add/remove the virtual link again.
-        VirtualLink virtualLink = manager.createVirtualLink(virtualNetwork1.id(), src, dst, TunnelId.valueOf(0));
+        VirtualLink virtualLink = manager.createVirtualLink(virtualNetwork1.id(), src, dst);
         manager.removeVirtualLink(virtualLink.networkId(), virtualLink.src(), virtualLink.dst());
         virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
         assertTrue("The virtual link set should be empty.", virtualLinks.isEmpty());
     }
 
     /**
+     * Tests adding the same virtual link twice.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testAddSameVirtualLink() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork1 = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DeviceId.deviceId(deviceIdValue1));
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DeviceId.deviceId(deviceIdValue2));
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+    }
+
+    /**
      * Tests add and remove of virtual ports.
      */
     @Test