ONOS-4077: REST API's for  virtual links.

Change-Id: Idc838f24735e75ad2729393a03dcac4d256239bb
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index 5cfac90..c03a3fa 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -28,6 +28,7 @@
 import org.onosproject.core.Application;
 import org.onosproject.incubator.net.virtual.TenantId;
 import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualLink;
 import org.onosproject.incubator.net.virtual.VirtualNetwork;
 import org.onosproject.incubator.net.virtual.VirtualPort;
 import org.onosproject.net.Annotations;
@@ -134,6 +135,7 @@
         registerCodec(VirtualNetwork.class, new VirtualNetworkCodec());
         registerCodec(VirtualDevice.class, new VirtualDeviceCodec());
         registerCodec(VirtualPort.class, new VirtualPortCodec());
+        registerCodec(VirtualLink.class, new VirtualLinkCodec());
         log.info("Started");
     }
 
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/VirtualLinkCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/VirtualLinkCodec.java
new file mode 100644
index 0000000..54f51e7
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/VirtualLinkCodec.java
@@ -0,0 +1,65 @@
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+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.VirtualLink;
+import org.onosproject.net.Link;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Codec for the VirtualLink class.
+ */
+public class VirtualLinkCodec extends JsonCodec<VirtualLink> {
+
+    // JSON field names
+    private static final String NETWORK_ID = "networkId";
+    private static final String TUNNEL_ID = "tunnelId";
+
+    private static final String NULL_OBJECT_MSG = "VirtualLink cannot be null";
+    private static final String MISSING_MEMBER_MSG = " member is required in VirtualLink";
+
+    @Override
+    public ObjectNode encode(VirtualLink vLink, CodecContext context) {
+        checkNotNull(vLink, NULL_OBJECT_MSG);
+
+        JsonCodec<Link> codec = context.codec(Link.class);
+        ObjectNode result = codec.encode(vLink, context);
+        result.put(NETWORK_ID, vLink.networkId().toString());
+        // TODO check if tunnelId needs to be part of VirtualLink interface.
+        if (vLink instanceof DefaultVirtualLink) {
+            result.put(TUNNEL_ID, ((DefaultVirtualLink) vLink).tunnelId().toString());
+        }
+        return result;
+    }
+
+    @Override
+    public VirtualLink decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+        JsonCodec<Link> codec = context.codec(Link.class);
+        Link link = codec.decode(json, context);
+        NetworkId nId = NetworkId.networkId(Long.parseLong(extractMember(NETWORK_ID, json)));
+        String tunnelIdStr = json.path(TUNNEL_ID).asText();
+        TunnelId tunnelId = tunnelIdStr != null ? TunnelId.valueOf(tunnelIdStr)
+                : TunnelId.valueOf(0);
+        return new DefaultVirtualLink(nId, link.src(), link.dst(), tunnelId);
+    }
+
+    /**
+     * Extract member from JSON ObjectNode.
+     *
+     * @param key key for which value is needed
+     * @param json JSON ObjectNode
+     * @return member value
+     */
+    private String extractMember(String key, ObjectNode json) {
+        return nullIsIllegal(json.get(key), key + MISSING_MEMBER_MSG).asText();
+    }
+}
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/TenantWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/TenantWebResource.java
index 201892a..dc15be5 100755
--- a/web/api/src/main/java/org/onosproject/rest/resources/TenantWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/TenantWebResource.java
@@ -131,7 +131,7 @@
      *
      * @param stream TenantId JSON stream
      * @return TenantId
-     * @throws IOException
+     * @throws IOException if unable to parse the request
      */
     private TenantId getTenantIdFromJsonStream(InputStream stream) throws IOException {
         ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
@@ -146,11 +146,11 @@
     /**
      * Get the matching tenant identifier from existing tenant identifiers in system.
      *
-     * @param vnetAdminSvc
+     * @param vnetAdminSvc virtual network administration service
      * @param tidIn tenant identifier
      * @return TenantId
      */
-    private static TenantId getExistingTenantId(VirtualNetworkAdminService vnetAdminSvc,
+     static TenantId getExistingTenantId(VirtualNetworkAdminService vnetAdminSvc,
                                                TenantId tidIn) {
         final TenantId resultTid = vnetAdminSvc
                 .getTenantIds()
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/VirtualNetworkWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/VirtualNetworkWebResource.java
index 1ebfe56..8df8722 100755
--- a/web/api/src/main/java/org/onosproject/rest/resources/VirtualNetworkWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/VirtualNetworkWebResource.java
@@ -18,9 +18,12 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+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.TenantId;
 import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualLink;
 import org.onosproject.incubator.net.virtual.VirtualNetwork;
 import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
 import org.onosproject.incubator.net.virtual.VirtualNetworkService;
@@ -38,6 +41,7 @@
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -69,7 +73,6 @@
     UriInfo uriInfo;
 
     // VirtualNetwork
-    // TODO Query vnets by tenant
 
     /**
      * Returns all virtual networks.
@@ -88,6 +91,22 @@
     }
 
     /**
+     * Returns the virtual networks with the specified tenant identifier.
+     *
+     * @param tenantId tenant identifier
+     * @return 200 OK, 404 not found
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{tenantId}")
+    public Response getVirtualNetworkById(@PathParam("tenantId") String tenantId) {
+        final TenantId existingTid = TenantWebResource.getExistingTenantId(vnetAdminService,
+                TenantId.tenantId(tenantId));
+        Set<VirtualNetwork> vnets = vnetService.getVirtualNetworks(existingTid);
+        return ok(encodeArray(VirtualNetwork.class, "vnets", vnets)).build();
+    }
+
+    /**
      * Creates a virtual network from the JSON input stream.
      *
      * @param stream TenantId JSON stream
@@ -122,7 +141,7 @@
     @DELETE
     @Path("{networkId}")
     public Response removeVirtualNetwork(@PathParam("networkId") long networkId) {
-        final NetworkId nid = NetworkId.networkId(networkId);
+        NetworkId nid = NetworkId.networkId(networkId);
         vnetAdminService.removeVirtualNetwork(nid);
         return Response.ok().build();
     }
@@ -139,7 +158,7 @@
     @Produces(MediaType.APPLICATION_JSON)
     @Path("{networkId}/devices")
     public Response getVirtualDevices(@PathParam("networkId") long networkId) {
-        final NetworkId nid = NetworkId.networkId(networkId);
+        NetworkId nid = NetworkId.networkId(networkId);
         Set<VirtualDevice> vdevs  = vnetService.getVirtualDevices(nid);
         return ok(encodeArray(VirtualDevice.class, "devices", vdevs)).build();
     }
@@ -154,7 +173,7 @@
      * @onos.rsModel VirtualDevice
      */
     @POST
-    @Path("{networkId}/devices/")
+    @Path("{networkId}/devices")
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     public Response createVirtualDevice(@PathParam("networkId") long networkId,
@@ -162,15 +181,14 @@
         try {
             ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
             final VirtualDevice vdevReq = codec(VirtualDevice.class).decode(jsonTree, this);
-            JsonNode specifiedRegionId = jsonTree.get("networkId");
-            if (specifiedRegionId != null &&
-                    specifiedRegionId.asLong() != (networkId)) {
+            JsonNode specifiedNetworkId = jsonTree.get("networkId");
+            if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
                 throw new IllegalArgumentException(INVALID_FIELD + "networkId");
             }
             final VirtualDevice vdevRes = vnetAdminService.createVirtualDevice(vdevReq.networkId(),
                                                                                vdevReq.id());
             UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
-                    .path("vnets").path(specifiedRegionId.asText())
+                    .path("vnets").path(specifiedNetworkId.asText())
                     .path("devices").path(vdevRes.id().toString());
             return Response
                     .created(locationBuilder.build())
@@ -191,8 +209,8 @@
     @Path("{networkId}/devices/{deviceId}")
     public Response removeVirtualDevice(@PathParam("networkId") long networkId,
             @PathParam("deviceId") String deviceId) {
-        final NetworkId nid = NetworkId.networkId(networkId);
-        final DeviceId did = DeviceId.deviceId(deviceId);
+        NetworkId nid = NetworkId.networkId(networkId);
+        DeviceId did = DeviceId.deviceId(deviceId);
         vnetAdminService.removeVirtualDevice(nid, did);
         return Response.ok().build();
     }
@@ -211,7 +229,7 @@
     @Path("{networkId}/devices/{deviceId}/ports")
     public Response getVirtualPorts(@PathParam("networkId") long networkId,
             @PathParam("deviceId") String deviceId) {
-        final NetworkId nid = NetworkId.networkId(networkId);
+        NetworkId nid = NetworkId.networkId(networkId);
         Iterable<VirtualPort> vports  = vnetService.getVirtualPorts(nid, DeviceId.deviceId(deviceId));
         return ok(encodeArray(VirtualPort.class, "ports", vports)).build();
     }
@@ -238,12 +256,10 @@
 //            final VirtualPort vportReq = codec(VirtualPort.class).decode(jsonTree, this);
             JsonNode specifiedNetworkId = jsonTree.get("networkId");
             JsonNode specifiedDeviceId = jsonTree.get("deviceId");
-            if (specifiedNetworkId != null &&
-                    specifiedNetworkId.asLong() != (networkId)) {
+            if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
                 throw new IllegalArgumentException(INVALID_FIELD + "networkId");
             }
-            if (specifiedDeviceId != null &&
-                    !specifiedDeviceId.asText().equals(virtDeviceId)) {
+            if (specifiedDeviceId == null || !specifiedDeviceId.asText().equals(virtDeviceId)) {
                 throw new IllegalArgumentException(INVALID_FIELD + "deviceId");
             }
             JsonNode specifiedPortNum = jsonTree.get("portNum");
@@ -283,13 +299,129 @@
     public Response removeVirtualPort(@PathParam("networkId") long networkId,
             @PathParam("deviceId") String deviceId,
             @PathParam("portNum") long portNum) {
-        final NetworkId nid = NetworkId.networkId(networkId);
+        NetworkId nid = NetworkId.networkId(networkId);
         vnetAdminService.removeVirtualPort(nid, DeviceId.deviceId(deviceId),
                 PortNumber.portNumber(portNum));
         return Response.ok().build();
     }
 
-    // TODO VirtualLink
+    // VirtualLink
+
+    /**
+     * Returns all virtual network links in a virtual network.
+     *
+     * @param networkId network identifier
+     * @return 200 OK
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/links")
+    public Response getVirtualLinks(@PathParam("networkId") long networkId) {
+        NetworkId nid = NetworkId.networkId(networkId);
+        Set<VirtualLink> vlinks  = vnetService.getVirtualLinks(nid);
+        return ok(encodeArray(VirtualLink.class, "links", vlinks)).build();
+    }
+
+     /**
+     * Creates a virtual network link from the JSON input stream.
+     *
+     * @param networkId network identifier
+     * @param stream Virtual device JSON stream
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel VirtualLink
+     */
+    @POST
+    @Path("{networkId}/links")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createVirtualLink(@PathParam("networkId") long networkId,
+                                      InputStream stream) {
+        try {
+            ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+            JsonNode specifiedNetworkId = jsonTree.get("networkId");
+            if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+            }
+            final VirtualLink vlinkReq = codec(VirtualLink.class).decode(jsonTree, this);
+            TunnelId tunnelId = TunnelId.valueOf(0);
+            if (vlinkReq instanceof DefaultVirtualLink) {
+                tunnelId = ((DefaultVirtualLink) vlinkReq).tunnelId();
+            }
+            vnetAdminService.createVirtualLink(vlinkReq.networkId(),
+                    vlinkReq.src(), vlinkReq.dst(), tunnelId);
+            UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                    .path("vnets").path(specifiedNetworkId.asText())
+                    .path("links");
+            return Response
+                    .created(locationBuilder.build())
+                    .build();
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Removes the virtual network link from the JSON input stream.
+     *
+     * @param networkId network identifier
+     * @param stream deviceIds JSON stream
+     * @return 200 OK, 404 not found
+     * @onos.rsModel VirtualLink
+     */
+    @DELETE
+    @Path("{networkId}/links")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response removeVirtualLink(@PathParam("networkId") long networkId,
+                                        InputStream stream) {
+        try {
+            ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+            JsonNode specifiedNetworkId = jsonTree.get("networkId");
+            if (specifiedNetworkId != null &&
+                    specifiedNetworkId.asLong() != (networkId)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+            }
+            final VirtualLink vlinkReq = codec(VirtualLink.class).decode(jsonTree, this);
+            vnetAdminService.removeVirtualLink(vlinkReq.networkId(),
+                    vlinkReq.src(), vlinkReq.dst());
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.ok().build();
+    }
+
+    /**
+     * Removes the virtual network link from the JSON input stream.
+     *
+     * @param networkId network identifier
+     * @param stream deviceIds JSON stream
+     * @return 200 OK, 404 not found
+     * @onos.rsModel VirtualLink
+     */
+
+    @PUT
+    @Path("{networkId}/links/remove")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response removeVirtualLink2(@PathParam("networkId") long networkId,
+                                        InputStream stream) {
+        try {
+            ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+            JsonNode specifiedNetworkId = jsonTree.get("networkId");
+            if (specifiedNetworkId != null &&
+                    specifiedNetworkId.asLong() != (networkId)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+            }
+            final VirtualLink vlinkReq = codec(VirtualLink.class).decode(jsonTree, this);
+            vnetAdminService.removeVirtualLink(vlinkReq.networkId(),
+                    vlinkReq.src(), vlinkReq.dst());
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.ok().build();
+    }
 
     /**
      * Get the tenant identifier from the JSON stream.
@@ -297,7 +429,7 @@
      * @param stream TenantId JSON stream
      * @param jsonFieldName field name
      * @return JsonNode
-     * @throws IOException
+     * @throws IOException if unable to parse the request
      */
     private JsonNode getFromJsonStream(InputStream stream, String jsonFieldName) throws IOException {
         ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
diff --git a/web/api/src/main/resources/definitions/VirtualLink.json b/web/api/src/main/resources/definitions/VirtualLink.json
new file mode 100644
index 0000000..2bc7be7
--- /dev/null
+++ b/web/api/src/main/resources/definitions/VirtualLink.json
@@ -0,0 +1,65 @@
+{
+  "type": "object",
+  "title": "vlink",
+  "required": [
+    "networkId",
+    "src",
+    "dst",
+    "type",
+    "state"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "String",
+      "example": "Network identifier"
+    },
+    "src": {
+      "type": "object",
+      "title": "src",
+      "required": [
+        "port",
+        "device"
+      ],
+      "properties": {
+        "port": {
+          "type": "string",
+          "example": "3"
+        },
+        "device": {
+          "type": "string",
+          "example": "of:0000000000000002"
+        }
+      }
+    },
+    "dst": {
+      "type": "object",
+      "title": "dst",
+      "required": [
+        "port",
+        "device"
+      ],
+      "properties": {
+        "port": {
+          "type": "string",
+          "example": "2"
+        },
+        "device": {
+          "type": "string",
+          "example": "of:0000000000000003"
+        }
+      }
+    },
+    "type": {
+      "type": "string",
+      "example": "DIRECT"
+    },
+    "state": {
+      "type": "string",
+      "example": "ACTIVE"
+    },
+    "tunnelId": {
+      "type": "int64",
+      "example": "Tunnel identifier"
+    }
+  }
+}
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/VirtualNetworkWebResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/VirtualNetworkWebResourceTest.java
index 72daa8b..58f5ab4 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/VirtualNetworkWebResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/VirtualNetworkWebResourceTest.java
@@ -32,16 +32,20 @@
 import org.onlab.rest.BaseResource;
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.incubator.net.tunnel.TunnelId;
 import org.onosproject.incubator.net.virtual.DefaultVirtualDevice;
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
 import org.onosproject.incubator.net.virtual.DefaultVirtualNetwork;
 import org.onosproject.incubator.net.virtual.DefaultVirtualPort;
 import org.onosproject.incubator.net.virtual.NetworkId;
 import org.onosproject.incubator.net.virtual.TenantId;
 import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualLink;
 import org.onosproject.incubator.net.virtual.VirtualNetwork;
 import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
 import org.onosproject.incubator.net.virtual.VirtualNetworkService;
 import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.DefaultPort;
@@ -61,6 +65,7 @@
 import java.net.HttpURLConnection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
@@ -80,7 +85,6 @@
     private CodecManager codecService;
 
     final HashSet<TenantId> tenantIdSet = new HashSet<>();
-    final HashSet<VirtualNetwork> vnetSet = new HashSet<>();
     final HashSet<VirtualDevice> vdevSet = new HashSet<>();
     final HashSet<VirtualPort> vportSet = new HashSet<>();
 
@@ -90,6 +94,7 @@
     private static final String PORT_NUM = "portNum";
     private static final String PHYS_DEVICE_ID = "physDeviceId";
     private static final String PHYS_PORT_NUM = "physPortNum";
+    private static final String TUNNEL_ID = "tunnelId";
 
     private final TenantId tenantId1 = TenantId.tenantId("TenantId1");
     private final TenantId tenantId2 = TenantId.tenantId("TenantId2");
@@ -106,8 +111,8 @@
     private final VirtualNetwork vnet3 = new DefaultVirtualNetwork(networkId3, tenantId3);
     private final VirtualNetwork vnet4 = new DefaultVirtualNetwork(networkId4, tenantId3);
 
-    private final DeviceId devId1 = DeviceId.deviceId("devId1");
-    private final DeviceId devId2 = DeviceId.deviceId("devId2");
+    private final DeviceId devId1 = DeviceId.deviceId("devid1");
+    private final DeviceId devId2 = DeviceId.deviceId("devid2");
     private final DeviceId devId22 = DeviceId.deviceId("dev22");
 
     private final VirtualDevice vdev1 = new DefaultVirtualDevice(networkId3, devId1);
@@ -115,6 +120,7 @@
 
     private final Device dev1 = NetTestTools.device("dev1");
     private final Device dev2 = NetTestTools.device("dev2");
+    private final Device dev21 = NetTestTools.device("dev21");
     private final Device dev22 = NetTestTools.device("dev22");
 
     Port port1 = new DefaultPort(dev1, portNumber(1), true);
@@ -125,6 +131,15 @@
     private final VirtualPort vport23 = new DefaultVirtualPort(networkId3,
                                                               dev22, portNumber(23), port2);
 
+    private final ConnectPoint cp11 = NetTestTools.connectPoint(devId1.toString(), 21);
+    private final ConnectPoint cp21 = NetTestTools.connectPoint(devId2.toString(), 22);
+    private final ConnectPoint cp12 = NetTestTools.connectPoint(devId1.toString(), 2);
+    private final ConnectPoint cp22 = NetTestTools.connectPoint(devId2.toString(), 22);
+
+    private final TunnelId tunnelId = TunnelId.valueOf(31);
+    private final VirtualLink vlink1 = new DefaultVirtualLink(networkId3, cp22, cp11, tunnelId);
+    private final VirtualLink vlink2 = new DefaultVirtualLink(networkId3, cp12, cp21, tunnelId);
+
     /**
      * Sets up the global values for all the tests.
      */
@@ -313,12 +328,9 @@
      */
     @Test
     public void testGetVirtualNetworksArray() {
+        final Set<VirtualNetwork> vnetSet = ImmutableSet.of(vnet1, vnet2, vnet3, vnet4);
         expect(mockVnetAdminService.getTenantIds()).andReturn(ImmutableSet.of(tenantId3)).anyTimes();
         replay(mockVnetAdminService);
-        vnetSet.add(vnet1);
-        vnetSet.add(vnet2);
-        vnetSet.add(vnet3);
-        vnetSet.add(vnet4);
         expect(mockVnetService.getVirtualNetworks(tenantId3)).andReturn(vnetSet).anyTimes();
         replay(mockVnetService);
 
@@ -344,6 +356,64 @@
     }
 
     /**
+     * Tests the result of the REST API GET for virtual networks with tenant id.
+     */
+    @Test
+    public void testGetVirtualNetworksByTenantId() {
+        final Set<VirtualNetwork> vnetSet = ImmutableSet.of(vnet1, vnet2, vnet3, vnet4);
+        expect(mockVnetAdminService.getTenantIds()).andReturn(ImmutableSet.of(tenantId3)).anyTimes();
+        replay(mockVnetAdminService);
+        expect(mockVnetService.getVirtualNetworks(tenantId3)).andReturn(vnetSet).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String response = wt.path("vnets/" + tenantId3.id()).request().get(String.class);
+        assertThat(response, containsString("{\"vnets\":["));
+
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("vnets"));
+
+        final JsonArray vnetJsonArray = result.get("vnets").asArray();
+        assertThat(vnetJsonArray, notNullValue());
+        assertEquals("Virtual networks array is not the correct size.",
+                     vnetSet.size(), vnetJsonArray.size());
+
+        vnetSet.forEach(vnet -> assertThat(vnetJsonArray, hasVnet(vnet)));
+
+        verify(mockVnetService);
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests the result of the REST API GET for virtual networks with tenant id.
+     */
+    @Test
+    public void testGetVirtualNetworksByNonExistentTenantId() {
+        String tenantIdName = "NON_EXISTENT_TENANT_ID";
+        expect(mockVnetAdminService.getTenantIds()).andReturn(ImmutableSet.of(tenantId3)).anyTimes();
+        replay(mockVnetAdminService);
+        expect(mockVnetService.getVirtualNetworks(anyObject())).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+
+        try {
+            wt.path("vnets/" + tenantIdName)
+                    .request()
+                    .get(String.class);
+            fail("Get of a non-existent virtual network did not throw an exception");
+        } catch (NotFoundException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 404 Not Found"));
+        }
+
+        verify(mockVnetService);
+        verify(mockVnetAdminService);
+    }
+
+    /**
      * Tests adding of new virtual network using POST via JSON stream.
      */
     @Test
@@ -756,5 +826,243 @@
         verify(mockVnetAdminService);
     }
 
-    // TODO Tests for Virtual Links
+    // Tests for Virtual Links
+
+    /**
+     * Tests the result of the REST API GET when there are no virtual links.
+     */
+    @Test
+    public void testGetVirtualLinksEmptyArray() {
+        NetworkId networkId = networkId4;
+        expect(mockVnetService.getVirtualLinks(networkId)).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString() + "/links";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, is("{\"links\":[]}"));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Tests the result of the REST API GET when virtual links are defined.
+     */
+    @Test
+    public void testGetVirtualLinksArray() {
+        NetworkId networkId = networkId3;
+        final Set<VirtualLink> vlinkSet = ImmutableSet.of(vlink1, vlink2);
+        expect(mockVnetService.getVirtualLinks(networkId)).andReturn(vlinkSet).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString() + "/links";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, containsString("{\"links\":["));
+
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("links"));
+
+        final JsonArray vnetJsonArray = result.get("links").asArray();
+        assertThat(vnetJsonArray, notNullValue());
+        assertEquals("Virtual links array is not the correct size.",
+                     vlinkSet.size(), vnetJsonArray.size());
+
+        vlinkSet.forEach(vlink -> assertThat(vnetJsonArray, hasVlink(vlink)));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual link representation in JSON matches
+     * the actual virtual link.
+     */
+    public static class VirtualLinkJsonMatcher extends LinksResourceTest.LinkJsonMatcher {
+        private final VirtualLink vlink;
+        private String reason = "";
+
+        public VirtualLinkJsonMatcher(VirtualLink vlinkValue) {
+            super(vlinkValue);
+            vlink = vlinkValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonObject jsonLink) {
+            if (!super.matchesSafely(jsonLink)) {
+                return false;
+            }
+            // check NetworkId
+            String jsonNetworkId = jsonLink.get(ID).asString();
+            String networkId = vlink.networkId().toString();
+            if (!jsonNetworkId.equals(networkId)) {
+                reason = ID + " was " + jsonNetworkId;
+                return false;
+            }
+            // check TunnelId
+            String jsonTunnelId = jsonLink.get(TUNNEL_ID).asString();
+            if (jsonTunnelId != null && vlink instanceof DefaultVirtualLink) {
+                String tunnelId = ((DefaultVirtualLink) vlink).tunnelId().toString();
+                if (!jsonTunnelId.equals(tunnelId)) {
+                    reason = TUNNEL_ID + " was " + jsonTunnelId;
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual link matcher.
+     *
+     * @param vlink virtual link object we are looking for
+     * @return matcher
+     */
+    private static VirtualLinkJsonMatcher matchesVirtualLink(VirtualLink vlink) {
+        return new VirtualLinkJsonMatcher(vlink);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual link is represented properly in a JSON
+     * array of links.
+     */
+    private static class VirtualLinkJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+        private final VirtualLink vlink;
+        private String reason = "";
+
+        public VirtualLinkJsonArrayMatcher(VirtualLink vlinkValue) {
+            vlink = vlinkValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonArray json) {
+            final int expectedAttributes = 2;
+
+            for (int jsonLinkIndex = 0; jsonLinkIndex < json.size();
+                 jsonLinkIndex++) {
+
+                JsonObject jsonLink = json.get(jsonLinkIndex).asObject();
+
+                if (matchesVirtualLink(vlink).matchesSafely(jsonLink)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual link array matcher.
+     *
+     * @param vlink virtual link object we are looking for
+     * @return matcher
+     */
+    private VirtualLinkJsonArrayMatcher hasVlink(VirtualLink vlink) {
+        return new VirtualLinkJsonArrayMatcher(vlink);
+    }
+
+    /**
+     * Tests adding of new virtual link using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualLink() {
+        NetworkId networkId = networkId3;
+        expect(mockVnetAdminService.createVirtualLink(networkId, cp22, cp11, tunnelId))
+                .andReturn(vlink1);
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-link.json");
+        String reqLocation = "vnets/" + networkId.toString() + "/links";
+        Response response = wt.path(reqLocation).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
+
+        String location = response.getLocation().getPath();
+        assertThat(location, Matchers.startsWith("/" + reqLocation));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests adding of a null virtual link using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualLinkNullJsonStream() {
+        NetworkId networkId = networkId3;
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        try {
+            String reqLocation = "vnets/" + networkId.toString() + "/links";
+            wt.path(reqLocation)
+                    .request(MediaType.APPLICATION_JSON_TYPE)
+                    .post(Entity.json(null), String.class);
+            fail("POST of null virtual link did not throw an exception");
+        } catch (BadRequestException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 400 Bad Request"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a virtual link with DELETE request.
+     */
+    @Test
+    public void testDeleteVirtualLink() {
+        NetworkId networkId = networkId3;
+        mockVnetAdminService.removeVirtualLink(networkId, cp22, cp11);
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-link.json");
+        String reqLocation = "vnets/" + networkId.toString() + "/links";
+        Response response = wt.path(reqLocation).request().accept(MediaType.APPLICATION_JSON_TYPE)
+                .method("DELETE", Entity.json(jsonStream));
+//        Response response = wt.path(reqLocation).request().method("DELETE", Entity.json(jsonStream));
+
+//        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+//        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a virtual link with PUT request.
+     */
+    @Test
+    public void testDeleteVirtualLink2() {
+        NetworkId networkId = networkId3;
+        mockVnetAdminService.removeVirtualLink(networkId, cp22, cp11);
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-link.json");
+        String reqLocation = "vnets/" + networkId.toString() + "/links/remove";
+        Response response = wt.path(reqLocation).request().accept(MediaType.APPLICATION_JSON_TYPE)
+                .method("PUT", Entity.json(jsonStream));
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+        verify(mockVnetAdminService);
+    }
+
+    // All Tests done
 }
diff --git a/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-link.json b/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-link.json
new file mode 100644
index 0000000..a7c5d26
--- /dev/null
+++ b/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-link.json
@@ -0,0 +1,14 @@
+{
+  "networkId": "3",
+  "src": {
+    "device": "of:devid2",
+    "port": "22" 
+  },
+  "dst": {
+    "device": "of:devid1",
+    "port": "21" 
+  },
+  "type": "VIRTUAL",
+  "state": "ACTIVE",
+  "tunnelId": "31"
+}