REST API's for tenants, virtual networks, virtual devices ad virtual ports.

Change-Id: I80abe14a083fce3dc6246118af8874028109388f

REST API's for tenants, virtual networks, virtual devices ad virtual ports.

Change-Id: Ib6c3d69d396e57822bae23f5bf3101c8b9c0b95c
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 2223970..71eef0f 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
@@ -26,6 +26,10 @@
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.JsonCodec;
 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.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualPort;
 import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -126,6 +130,10 @@
         registerCodec(McastRoute.class, new McastRouteCodec());
         registerCodec(DeviceKey.class, new DeviceKeyCodec());
         registerCodec(Region.class, new RegionCodec());
+        registerCodec(TenantId.class, new TenantIdCodec());
+        registerCodec(VirtualNetwork.class, new VirtualNetworkCodec());
+        registerCodec(VirtualDevice.class, new VirtualDeviceCodec());
+        registerCodec(VirtualPort.class, new VirtualPortCodec());
         log.info("Started");
     }
 
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/TenantIdCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/TenantIdCodec.java
new file mode 100644
index 0000000..6d43eb2
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/TenantIdCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.virtual.TenantId;
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Codec for the TenantId class.
+ */
+public class TenantIdCodec extends JsonCodec<TenantId> {
+
+    // JSON field names
+    private static final String TENANT_ID = "id";
+
+    private static final String NULL_TENANT_MSG = "TenantId cannot be null";
+    private static final String MISSING_MEMBER_MSG = " member is required in TenantId";
+
+    @Override
+    public ObjectNode encode(TenantId tenantId, CodecContext context) {
+        checkNotNull(tenantId, NULL_TENANT_MSG);
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(TENANT_ID, tenantId.id().toString());
+
+        return result;
+    }
+
+    @Override
+    public TenantId decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        TenantId tenantId = TenantId.tenantId(extractMember(TENANT_ID, json));
+
+        return tenantId;
+    }
+
+    private String extractMember(String key, ObjectNode json) {
+        return nullIsIllegal(json.get(key), key + MISSING_MEMBER_MSG).asText();
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/VirtualDeviceCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/VirtualDeviceCodec.java
new file mode 100644
index 0000000..fb1dac6
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/VirtualDeviceCodec.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.virtual.DefaultVirtualDevice;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.net.DeviceId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Codec for the VirtualDevice class.
+ */
+public class VirtualDeviceCodec extends JsonCodec<VirtualDevice> {
+
+    // JSON field names
+    private static final String ID = "deviceId";
+    private static final String NETWORK_ID = "networkId";
+
+    private static final String NULL_OBJECT_MSG = "VirtualDevice cannot be null";
+    private static final String MISSING_MEMBER_MSG = " member is required in VirtualDevice";
+
+    @Override
+    public ObjectNode encode(VirtualDevice vDev, CodecContext context) {
+        checkNotNull(vDev, NULL_OBJECT_MSG);
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(ID, vDev.id().toString())
+                .put(NETWORK_ID, vDev.networkId().toString());
+
+        return result;
+    }
+
+    @Override
+    public VirtualDevice decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        DeviceId dId = DeviceId.deviceId(extractMember(ID, json));
+        NetworkId nId = NetworkId.networkId(Long.parseLong(extractMember(NETWORK_ID, json)));
+        return new DefaultVirtualDevice(nId, dId);
+    }
+
+    private String extractMember(String key, ObjectNode json) {
+        return nullIsIllegal(json.get(key), key + MISSING_MEMBER_MSG).asText();
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/VirtualNetworkCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/VirtualNetworkCodec.java
new file mode 100644
index 0000000..6e5285e
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/VirtualNetworkCodec.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.virtual.DefaultVirtualNetwork;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Codec for the VirtualNetwork class.
+ */
+public class VirtualNetworkCodec extends JsonCodec<VirtualNetwork> {
+
+    // JSON field names
+    private static final String NETWORK_ID = "networkId";
+    private static final String TENANT_ID = "tenantId";
+
+    private static final String NULL_OBJECT_MSG = "VirtualNetwork cannot be null";
+    private static final String MISSING_MEMBER_MSG = " member is required in VirtualNetwork";
+
+    @Override
+    public ObjectNode encode(VirtualNetwork vnet, CodecContext context) {
+        checkNotNull(vnet, NULL_OBJECT_MSG);
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(NETWORK_ID, vnet.id().toString())
+                .put(TENANT_ID, vnet.tenantId().toString());
+
+        return result;
+    }
+
+    @Override
+    public VirtualNetwork decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        NetworkId nId = NetworkId.networkId(Long.parseLong(extractMember(NETWORK_ID, json)));
+        TenantId tId = TenantId.tenantId(extractMember(TENANT_ID, json));
+        return new DefaultVirtualNetwork(nId, tId);
+    }
+
+    private String extractMember(String key, ObjectNode json) {
+        return nullIsIllegal(json.get(key), key + MISSING_MEMBER_MSG).asText();
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/VirtualPortCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/VirtualPortCodec.java
new file mode 100644
index 0000000..df20366
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/VirtualPortCodec.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.virtual.DefaultVirtualPort;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Codec for the VirtualPort class.
+ */
+public class VirtualPortCodec extends JsonCodec<VirtualPort> {
+
+    // JSON field names
+    private static final String NETWORK_ID = "networkId";
+    private static final String DEVICE_ID = "deviceId";
+    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 NULL_OBJECT_MSG = "VirtualPort cannot be null";
+    private static final String MISSING_MEMBER_MSG = " member is required in VirtualPort";
+    private static final String INVALID_VIRTUAL_DEVICE = " is not a valid VirtualDevice";
+
+    @Override
+    public ObjectNode encode(VirtualPort vPort, CodecContext context) {
+        checkNotNull(vPort, NULL_OBJECT_MSG);
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(NETWORK_ID, vPort.networkId().toString())
+                .put(DEVICE_ID, vPort.element().id().toString())
+                .put(PORT_NUM, vPort.number().toString())
+                .put(PHYS_DEVICE_ID, vPort.realizedBy().element().id().toString())
+                .put(PHYS_PORT_NUM, vPort.realizedBy().number().toString());
+
+        return result;
+    }
+
+    @Override
+    public VirtualPort decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        NetworkId nId = NetworkId.networkId(Long.parseLong(extractMember(NETWORK_ID, json)));
+        DeviceId dId = DeviceId.deviceId(extractMember(DEVICE_ID, json));
+
+        VirtualNetworkService vnetService = context.getService(VirtualNetworkService.class);
+        Set<VirtualDevice> vDevs = vnetService.getVirtualDevices(nId);
+        VirtualDevice vDev = vDevs.stream()
+                .filter(virtualDevice -> virtualDevice.id().equals(dId))
+                .findFirst().orElse(null);
+        nullIsIllegal(vDev, dId.toString() + INVALID_VIRTUAL_DEVICE);
+
+        PortNumber portNum = PortNumber.portNumber(extractMember(PORT_NUM, json));
+        DeviceId physDId = DeviceId.deviceId(extractMember(PHYS_DEVICE_ID, json));
+        PortNumber physPortNum = PortNumber.portNumber(extractMember(PHYS_PORT_NUM, json));
+
+        DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+        Device physDevice = new DefaultDevice(null, physDId,
+                null, null, null, null, null, null, annotations);
+        Port realizedBy = new DefaultPort(physDevice, physPortNum, true);
+        return new DefaultVirtualPort(nId, vDev, portNum, realizedBy);
+    }
+
+    private String extractMember(String key, ObjectNode json) {
+        return nullIsIllegal(json.get(key), key + MISSING_MEMBER_MSG).asText();
+    }
+}
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java b/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java
index 179bb2c..4521658 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java
@@ -22,7 +22,7 @@
  * Representation of a virtual port.
  */
 @Beta
-public interface VirtualPort extends Port {
+public interface VirtualPort extends VirtualElement, Port {
 
     /**
      * Returns the underlying port using which this port is realized.
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java b/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java
index 1fa6f79..4d45a30 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java
@@ -47,7 +47,9 @@
                 FlowObjectiveWebResource.class,
                 MulticastRouteWebResource.class,
                 DeviceKeyWebResource.class,
-                RegionsWebResource.class
+                RegionsWebResource.class,
+                TenantWebResource.class,
+                VirtualNetworkWebResource.class
         );
     }
 }
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
new file mode 100755
index 0000000..4ba8d44
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/rest/resources/TenantWebResource.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016 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.rest.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.incubator.net.virtual.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Query and manage tenants of virtual networks.
+ */
+@Path("tenants")
+public class TenantWebResource extends AbstractWebResource {
+
+    private static final String MISSING_TENANTID = "Missing tenant identifier";
+    private static final String TENANTID_NOT_FOUND = "Tenant identifier not found";
+    private static final String INVALID_TENANTID = "Invalid tenant identifier ";
+
+    @Context
+    UriInfo uriInfo;
+
+    private final VirtualNetworkAdminService vnetAdminService = get(VirtualNetworkAdminService.class);
+
+    /**
+     * Returns all tenants.
+     *
+     * @return 200 OK
+     * @onos.rsModel TenantIds
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getVirtualNetworkTenants() {
+        Iterable<TenantId> tenantIds = vnetAdminService.getTenantIds();
+        return ok(encodeArray(TenantId.class, "tenants", tenantIds)).build();
+    }
+
+    /**
+     * Creates a tenant with the given tenant identifier.
+     *
+     * @param stream TenantId JSON stream
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel TenantId
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response addTenantId(InputStream stream) {
+        try {
+            final TenantId tid = getTenantIdFromJsonStream(stream);
+            vnetAdminService.registerTenantId(tid);
+            final TenantId resultTid = getExistingTenantId(vnetAdminService, tid);
+            UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                    .path("tenants")
+                    .path(resultTid.id());
+            return Response
+                    .created(locationBuilder.build())
+                    .build();
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Removes the specified tenant with the specified tenant identifier.
+     *
+     * @param tenantId tenant identifier
+     * @return 200 OK, 404 not found
+     */
+    @DELETE
+    @Path("{tenantId}")
+    public Response removeTenantId(@PathParam("tenantId") String tenantId) {
+        final TenantId tid = TenantId.tenantId(tenantId);
+        final TenantId existingTid = getExistingTenantId(vnetAdminService, tid);
+        vnetAdminService.unregisterTenantId(existingTid);
+        return Response.ok().build();
+    }
+
+    /**
+     * Removes the specified tenant with the specified tenant identifier.
+     *
+     * @param stream deviceIds JSON stream
+     * @return 200 OK, 404 not found
+     * @onos.rsModel TenantId
+     */
+    @DELETE
+    public Response removeTenantId(InputStream stream) {
+        try {
+            final TenantId tid = getTenantIdFromJsonStream(stream);
+            vnetAdminService.unregisterTenantId(tid);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+        return Response.ok().build();
+    }
+
+    /**
+     * Get the tenant identifier from the JSON stream.
+     *
+     * @param stream TenantId JSON stream
+     * @return TenantId
+     * @throws IOException
+     */
+    private TenantId getTenantIdFromJsonStream(InputStream stream) throws IOException {
+        ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+        JsonNode specifiedTenantId = jsonTree.get("id");
+
+        if (specifiedTenantId == null) {
+            throw new IllegalArgumentException(MISSING_TENANTID);
+        }
+        return TenantId.tenantId(specifiedTenantId.asText());
+    }
+
+    /**
+     * Get the matching tenant identifier from existing tenant identifiers in system.
+     *
+     * @param vnetAdminSvc
+     * @param tidIn tenant identifier
+     * @return TenantId
+     */
+    private static TenantId getExistingTenantId(VirtualNetworkAdminService vnetAdminSvc,
+                                               TenantId tidIn) {
+        final TenantId resultTid = vnetAdminSvc
+                .getTenantIds()
+                .stream()
+                .filter(tenantId -> tenantId.equals(tidIn))
+                .findFirst()
+                .orElseThrow(() -> new ItemNotFoundException(TENANTID_NOT_FOUND));
+        return resultTid;
+    }
+}
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
new file mode 100755
index 0000000..62ec090
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/rest/resources/VirtualNetworkWebResource.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2016 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.rest.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+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.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.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Query and Manage Virtual Network elements.
+ */
+@Path("vnets")
+public class VirtualNetworkWebResource extends AbstractWebResource {
+
+    private static final String MISSING_FIELD = "Missing ";
+    private static final String INVALID_FIELD = "Invalid ";
+
+    private final VirtualNetworkAdminService vnetAdminService = get(VirtualNetworkAdminService.class);
+    private final VirtualNetworkService vnetService = get(VirtualNetworkService.class);
+
+    @Context
+    UriInfo uriInfo;
+
+    // VirtualNetwork
+    // TODO Query vnets by tenant
+
+    /**
+     * Returns all virtual networks.
+     *
+     * @return 200 OK
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getVirtualNetworks() {
+        Set<TenantId> tenantIds = vnetAdminService.getTenantIds();
+        List<VirtualNetwork> allVnets = tenantIds.stream()
+                .map(tenantId -> vnetService.getVirtualNetworks(tenantId))
+                .flatMap(Collection::stream)
+                .collect(Collectors.toList());
+        return ok(encodeArray(VirtualNetwork.class, "vnets", allVnets)).build();
+    }
+
+    /**
+     * Creates a virtual network from the JSON input stream.
+     *
+     * @param stream TenantId JSON stream
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel TenantId
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createVirtualNetwork(InputStream stream) {
+        try {
+            final TenantId tid = TenantId.tenantId(getFromJsonStream(stream, "id").asText());
+            VirtualNetwork newVnet = vnetAdminService.createVirtualNetwork(tid);
+            UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                    .path("vnets")
+                    .path(newVnet.id().toString());
+            return Response
+                    .created(locationBuilder.build())
+                    .build();
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Removes the virtual network with the specified network identifier.
+     *
+     * @param networkId network identifier
+     * @return 200 OK, 404 not found
+     */
+    @DELETE
+    @Path("{networkId}")
+    public Response removeVirtualNetwork(@PathParam("networkId") long networkId) {
+        final NetworkId nid = NetworkId.networkId(networkId);
+        vnetAdminService.removeVirtualNetwork(nid);
+        return Response.ok().build();
+    }
+
+    // VirtualDevice
+
+    /**
+     * Returns all virtual network devices in a virtual network.
+     *
+     * @param networkId network identifier
+     * @return 200 OK
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/devices")
+    public Response getVirtualDevices(@PathParam("networkId") long networkId) {
+        final NetworkId nid = NetworkId.networkId(networkId);
+        Set<VirtualDevice> vdevs  = vnetService.getVirtualDevices(nid);
+        return ok(encodeArray(VirtualDevice.class, "devices", vdevs)).build();
+    }
+
+    /**
+     * Creates a virtual device 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 VirtualDevice
+     */
+    @POST
+    @Path("{networkId}/devices/")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createVirtualDevice(@PathParam("networkId") long networkId,
+                                        InputStream stream) {
+        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)) {
+                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("devices").path(vdevRes.id().toString());
+            return Response
+                    .created(locationBuilder.build())
+                    .build();
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Removes the virtual network device from the virtual network.
+     *
+     * @param networkId network identifier
+     * @param deviceId device identifier
+     * @return 200 OK, 404 not found
+     */
+    @DELETE
+    @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);
+        vnetAdminService.removeVirtualDevice(nid, did);
+        return Response.ok().build();
+    }
+
+    // VirtualPort
+
+    /**
+     * Returns all virtual network ports in a virtual device in a virtual network.
+     *
+     * @param networkId network identifier
+     * @param deviceId virtual device identifier
+     * @return 200 OK
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/devices/{deviceId}/ports")
+    public Response getVirtualPorts(@PathParam("networkId") long networkId,
+            @PathParam("deviceId") String deviceId) {
+        final NetworkId nid = NetworkId.networkId(networkId);
+        Iterable<VirtualPort> vports  = vnetService.getVirtualPorts(nid, DeviceId.deviceId(deviceId));
+        return ok(encodeArray(VirtualPort.class, "ports", vports)).build();
+    }
+
+    /**
+     * Creates a virtual network port in a virtual device in a virtual network.
+     *
+     * @param networkId network identifier
+     * @param virtDeviceId virtual device 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 VirtualPort
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/devices/{deviceId}/ports")
+    public Response createVirtualPort(@PathParam("networkId") long networkId,
+            @PathParam("deviceId") String virtDeviceId,
+            InputStream stream) {
+        try {
+            ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+//            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)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+            }
+            if (specifiedDeviceId != null &&
+                    !specifiedDeviceId.asText().equals(virtDeviceId)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "deviceId");
+            }
+            JsonNode specifiedPortNum = jsonTree.get("portNum");
+            JsonNode specifiedPhysDeviceId = jsonTree.get("physDeviceId");
+            JsonNode specifiedPhysPortNum = jsonTree.get("physPortNum");
+            final NetworkId nid = NetworkId.networkId(networkId);
+            DeviceId vdevId = DeviceId.deviceId(virtDeviceId);
+            DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+            Device physDevice = new DefaultDevice(null, DeviceId.deviceId(specifiedPhysDeviceId.asText()),
+                    null, null, null, null, null, null, annotations);
+            Port realizedBy = new DefaultPort(physDevice,
+                    PortNumber.portNumber(specifiedPhysPortNum.asText()), true);
+            VirtualPort vport = vnetAdminService.createVirtualPort(nid, vdevId,
+                    PortNumber.portNumber(specifiedPortNum.asText()), realizedBy);
+            UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                    .path("vnets").path(specifiedNetworkId.asText())
+                    .path("devices").path(specifiedDeviceId.asText())
+                    .path("ports").path(vport.number().toString());
+            return Response
+                    .created(locationBuilder.build())
+                    .build();
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Removes the virtual network port from the virtual device in a virtual network.
+     *
+     * @param networkId network identifier
+     * @param deviceId virtual device identifier
+     * @param portNum virtual port number
+     * @return 200 OK, 404 not found
+     */
+    @DELETE
+    @Path("{networkId}/devices/{deviceId}/ports/{portNum}")
+    public Response removeVirtualPort(@PathParam("networkId") long networkId,
+            @PathParam("deviceId") String deviceId,
+            @PathParam("portNum") long portNum) {
+        final NetworkId nid = NetworkId.networkId(networkId);
+        vnetAdminService.removeVirtualPort(nid, DeviceId.deviceId(deviceId),
+                PortNumber.portNumber(portNum));
+        return Response.ok().build();
+    }
+
+    // TODO VirtualLink
+
+    /**
+     * Get the tenant identifier from the JSON stream.
+     *
+     * @param stream TenantId JSON stream
+     * @param jsonFieldName field name
+     * @return JsonNode
+     * @throws IOException
+     */
+    private JsonNode getFromJsonStream(InputStream stream, String jsonFieldName) throws IOException {
+        ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+        JsonNode jsonNode = jsonTree.get(jsonFieldName);
+
+        if (jsonNode == null) {
+            throw new IllegalArgumentException(MISSING_FIELD + jsonFieldName);
+        }
+        return jsonNode;
+    }
+}
diff --git a/web/api/src/main/resources/definitions/TenantId.json b/web/api/src/main/resources/definitions/TenantId.json
new file mode 100644
index 0000000..23981e8
--- /dev/null
+++ b/web/api/src/main/resources/definitions/TenantId.json
@@ -0,0 +1,13 @@
+{
+  "type": "object",
+  "title": "TenantId",
+  "required": [
+    "id"
+  ],
+  "properties": {
+    "id": {
+      "type": "String",
+      "example": "Tenant unique identifier"
+    }
+  }
+}
diff --git a/web/api/src/main/resources/definitions/TenantIds.json b/web/api/src/main/resources/definitions/TenantIds.json
new file mode 100644
index 0000000..91e104b
--- /dev/null
+++ b/web/api/src/main/resources/definitions/TenantIds.json
@@ -0,0 +1,29 @@
+{
+  "type": "object",
+  "title": "tenants",
+  "required": [
+    "tenants"
+  ],
+  "properties": {
+    "tenants": {
+      "type": "array",
+      "xml": {
+        "name": "tenants",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "tenant",
+        "required": [
+          "id"
+        ],
+        "properties": {
+          "id": {
+            "type": "String",
+            "example": "Tenant unique identifier"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/web/api/src/main/resources/definitions/VirtualDevice.json b/web/api/src/main/resources/definitions/VirtualDevice.json
new file mode 100644
index 0000000..858c49d
--- /dev/null
+++ b/web/api/src/main/resources/definitions/VirtualDevice.json
@@ -0,0 +1,18 @@
+{
+  "type": "object",
+  "title": "vdev",
+  "required": [
+    "networkId",
+    "deviceId"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "String",
+      "example": "Network identifier"
+    },
+    "deviceId": {
+      "type": "String",
+      "example": "Device identifier"
+    }
+  }
+}
diff --git a/web/api/src/main/resources/definitions/VirtualPort.json b/web/api/src/main/resources/definitions/VirtualPort.json
new file mode 100644
index 0000000..89cb6c1
--- /dev/null
+++ b/web/api/src/main/resources/definitions/VirtualPort.json
@@ -0,0 +1,33 @@
+{
+  "type": "object",
+  "title": "vport",
+  "required": [
+    "networkId",
+    "deviceId",
+    "portNum",
+    "physDeviceId",
+    "physPortNum"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "String",
+      "example": "Network identifier"
+    },
+    "deviceId": {
+      "type": "String",
+      "example": "Virtual device identifier"
+    },
+    "portNum": {
+      "type": "String",
+      "example": "Virtual device port number"
+    },
+    "physDeviceId": {
+      "type": "String",
+      "example": "Physical device identifier"
+    },
+    "physPortNum": {
+      "type": "String",
+      "example": "Physical device port number"
+    }
+  }
+}
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/TenantWebResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/TenantWebResourceTest.java
new file mode 100644
index 0000000..03b6e4e
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/rest/resources/TenantWebResourceTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2016 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.rest.resources;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonArray;
+import com.eclipsesource.json.JsonObject;
+import com.google.common.collect.ImmutableSet;
+import org.glassfish.jersey.client.ClientProperties;
+import org.hamcrest.Description;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.rest.BaseResource;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.incubator.net.virtual.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.HashSet;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for tenant REST APIs.
+ */
+public class TenantWebResourceTest extends ResourceTest {
+
+    private final VirtualNetworkAdminService mockVnetAdminService = createMock(VirtualNetworkAdminService.class);
+
+    final HashSet<TenantId> tenantIdSet = new HashSet<>();
+
+    private static final String ID = "id";
+
+    private final TenantId tenantId1 = TenantId.tenantId("TenantId1");
+    private final TenantId tenantId2 = TenantId.tenantId("TenantId2");
+    private final TenantId tenantId3 = TenantId.tenantId("TenantId3");
+    private final TenantId tenantId4 = TenantId.tenantId("TenantId4");
+
+    /**
+     * Sets up the global values for all the tests.
+     */
+    @Before
+    public void setUpTest() {
+        // Register the services needed for the test
+        CodecManager codecService = new CodecManager();
+        codecService.activate();
+        ServiceDirectory testDirectory =
+                new TestServiceDirectory()
+                        .add(VirtualNetworkAdminService.class, mockVnetAdminService)
+                        .add(CodecService.class, codecService);
+
+        BaseResource.setServiceDirectory(testDirectory);
+    }
+
+    /**
+     * Hamcrest matcher to check that a tenant id representation in JSON matches
+     * the actual tenant id.
+     */
+    public static class TenantIdJsonMatcher extends TypeSafeMatcher<JsonObject> {
+        private final TenantId tenantId;
+        private String reason = "";
+
+        public TenantIdJsonMatcher(TenantId tenantIdValue) {
+            tenantId = tenantIdValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonObject jsonHost) {
+            // Check the tenant id
+            final String jsonId = jsonHost.get(ID).asString();
+            if (!jsonId.equals(tenantId.id())) {
+                reason = ID + " " + tenantId.id();
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a tenant id array matcher.
+     *
+     * @param tenantId tenant id object we are looking for
+     * @return matcher
+     */
+    private static TenantIdJsonMatcher matchesTenantId(TenantId tenantId) {
+        return new TenantIdJsonMatcher(tenantId);
+    }
+
+    /**
+     * Hamcrest matcher to check that a tenant id is represented properly in a JSON
+     * array of tenant ids.
+     */
+    public static class TenantIdJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+        private final TenantId tenantId;
+        private String reason = "";
+
+        public TenantIdJsonArrayMatcher(TenantId tenantIdValue) {
+            tenantId = tenantIdValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonArray json) {
+            boolean tenantIdFound = false;
+            final int expectedAttributes = 1;
+            for (int tenantIdIndex = 0; tenantIdIndex < json.size();
+                 tenantIdIndex++) {
+
+                final JsonObject jsonHost = json.get(tenantIdIndex).asObject();
+
+                // Only 1 attribute - ID.
+                if (jsonHost.names().size() < expectedAttributes) {
+                    reason = "Found a tenant id with the wrong number of attributes";
+                    return false;
+                }
+
+                final String jsonDeviceKeyId = jsonHost.get(ID).asString();
+                if (jsonDeviceKeyId.equals(tenantId.id())) {
+                    tenantIdFound = true;
+
+                    //  We found the correct tenant id, check the tenant id attribute values
+                    assertThat(jsonHost, matchesTenantId(tenantId));
+                }
+            }
+            if (!tenantIdFound) {
+                reason = "Tenant id " + tenantId.id() + " was not found";
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a tenant id array matcher.
+     *
+     * @param tenantId tenant id object we are looking for
+     * @return matcher
+     */
+    private static TenantIdJsonArrayMatcher hasTenantId(TenantId tenantId) {
+        return new TenantIdJsonArrayMatcher(tenantId);
+    }
+
+    /**
+     * Tests the result of the REST API GET when there are no tenant ids.
+     */
+    @Test
+    public void testGetTenantsEmptyArray() {
+        expect(mockVnetAdminService.getTenantIds()).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        String response = wt.path("tenants").request().get(String.class);
+        assertThat(response, is("{\"tenants\":[]}"));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests the result of the REST API GET when tenant ids are defined.
+     */
+    @Test
+    public void testGetTenantIdsArray() {
+        tenantIdSet.add(tenantId1);
+        tenantIdSet.add(tenantId2);
+        tenantIdSet.add(tenantId3);
+        tenantIdSet.add(tenantId4);
+        expect(mockVnetAdminService.getTenantIds()).andReturn(tenantIdSet).anyTimes();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        String response = wt.path("tenants").request().get(String.class);
+        assertThat(response, containsString("{\"tenants\":["));
+
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("tenants"));
+
+        final JsonArray tenantIds = result.get("tenants").asArray();
+        assertThat(tenantIds, notNullValue());
+        assertEquals("Device keys array is not the correct size.",
+                     tenantIdSet.size(), tenantIds.size());
+
+        tenantIdSet.forEach(tenantId -> assertThat(tenantIds, hasTenantId(tenantId)));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests adding of new tenant id using POST via JSON stream.
+     */
+    @Test
+    public void testPost() {
+        mockVnetAdminService.registerTenantId(anyObject());
+        tenantIdSet.add(tenantId2);
+        expect(mockVnetAdminService.getTenantIds()).andReturn(tenantIdSet).anyTimes();
+        expectLastCall();
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = TenantWebResourceTest.class
+                .getResourceAsStream("post-tenant.json");
+
+        Response response = wt.path("tenants").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("/tenants/" + tenantId2));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests adding of a null tenant id using POST via JSON stream.
+     */
+    @Test
+    public void testPostNullTenantId() {
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        try {
+            String response = wt.path("tenants")
+                    .request(MediaType.APPLICATION_JSON_TYPE)
+                    .post(Entity.json(null), String.class);
+            fail("POST of null tenant id did not throw an exception");
+        } catch (BadRequestException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 400 Bad Request"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a tenant id with DELETE request.
+     */
+    @Test
+    public void testDelete() {
+        expect(mockVnetAdminService.getTenantIds())
+                .andReturn(ImmutableSet.of(tenantId2)).anyTimes();
+        mockVnetAdminService.unregisterTenantId(anyObject());
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        Response response = wt.path("tenants/" + tenantId2)
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a tenant id with DELETE request via JSON stream.
+     */
+    @Test
+    public void testDeleteViaJson() {
+        mockVnetAdminService.unregisterTenantId(anyObject());
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        InputStream jsonStream = TenantWebResourceTest.class
+                .getResourceAsStream("post-tenant.json");
+        Response response = wt.request().method("DELETE", Entity.json(jsonStream));
+
+//        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+
+//        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests that a DELETE of a non-existent tenant id throws an exception.
+     */
+    @Test
+    public void testDeleteNonExistentDeviceKey() {
+        expect(mockVnetAdminService.getTenantIds())
+                .andReturn(ImmutableSet.of())
+                .anyTimes();
+        expectLastCall();
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+
+        try {
+            wt.path("tenants/" + "NON_EXISTENT_TENANT_ID")
+                    .request()
+                    .delete(String.class);
+            fail("Delete of a non-existent tenant did not throw an exception");
+        } catch (NotFoundException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 404 Not Found"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+}
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
new file mode 100644
index 0000000..9581e3a
--- /dev/null
+++ b/web/api/src/test/java/org/onosproject/rest/resources/VirtualNetworkWebResourceTest.java
@@ -0,0 +1,760 @@
+/*
+ * Copyright 2016 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.rest.resources;
+
+import com.eclipsesource.json.Json;
+import com.eclipsesource.json.JsonArray;
+import com.eclipsesource.json.JsonObject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.glassfish.jersey.client.ClientProperties;
+import org.hamcrest.Description;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.rest.BaseResource;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.incubator.net.virtual.DefaultVirtualDevice;
+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.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.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Unit tests for virtual network REST APIs.
+ */
+public class VirtualNetworkWebResourceTest extends ResourceTest {
+
+    private final VirtualNetworkAdminService mockVnetAdminService = createMock(VirtualNetworkAdminService.class);
+    private final VirtualNetworkService mockVnetService = createMock(VirtualNetworkService.class);
+    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<>();
+
+    private static final String ID = "networkId";
+    private static final String TENANT_ID = "tenantId";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String PORT_NUM = "portNum";
+    private static final String PHYS_DEVICE_ID = "physDeviceId";
+    private static final String PHYS_PORT_NUM = "physPortNum";
+
+    private final TenantId tenantId1 = TenantId.tenantId("TenantId1");
+    private final TenantId tenantId2 = TenantId.tenantId("TenantId2");
+    private final TenantId tenantId3 = TenantId.tenantId("TenantId3");
+    private final TenantId tenantId4 = TenantId.tenantId("TenantId4");
+
+    private final NetworkId networkId1 = NetworkId.networkId(1);
+    private final NetworkId networkId2 = NetworkId.networkId(2);
+    private final NetworkId networkId3 = NetworkId.networkId(3);
+    private final NetworkId networkId4 = NetworkId.networkId(4);
+
+    private final VirtualNetwork vnet1 = new DefaultVirtualNetwork(networkId1, tenantId3);
+    private final VirtualNetwork vnet2 = new DefaultVirtualNetwork(networkId2, tenantId3);
+    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 devId22 = DeviceId.deviceId("dev22");
+
+    private final VirtualDevice vdev1 = new DefaultVirtualDevice(networkId3, devId1);
+    private final VirtualDevice vdev2 = new DefaultVirtualDevice(networkId3, devId2);
+
+    private final Device dev1 = NetTestTools.device("dev1");
+    private final Device dev2 = NetTestTools.device("dev2");
+    private final Device dev22 = NetTestTools.device("dev22");
+
+    Port port1 = new DefaultPort(dev1, portNumber(1), true);
+    Port port2 = new DefaultPort(dev2, portNumber(2), true);
+
+    private final VirtualPort vport22 = new DefaultVirtualPort(networkId3,
+                                                              dev22, portNumber(22), port1);
+    private final VirtualPort vport23 = new DefaultVirtualPort(networkId3,
+                                                              dev22, portNumber(23), port2);
+
+    /**
+     * Sets up the global values for all the tests.
+     */
+    @Before
+    public void setUpTest() {
+        // Register the services needed for the test
+        codecService = new CodecManager();
+        codecService.activate();
+        ServiceDirectory testDirectory =
+                new TestServiceDirectory()
+                        .add(VirtualNetworkAdminService.class, mockVnetAdminService)
+                        .add(VirtualNetworkService.class, mockVnetService)
+                        .add(CodecService.class, codecService);
+
+        BaseResource.setServiceDirectory(testDirectory);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual network entity representation in JSON matches
+     * the actual virtual network entity.
+     */
+    public static class JsonObjectMatcher<T> extends TypeSafeMatcher<JsonObject> {
+        private final T vnetEntity;
+        private List<String> jsonFieldNames;
+        private String reason = "";
+        private BiFunction<T, String, String> getValue; // get vnetEntity's value
+
+        public JsonObjectMatcher(T vnetEntityValue,
+                                 List<String> jsonFieldNames1,
+                                 BiFunction<T, String, String> getValue1) {
+            vnetEntity = vnetEntityValue;
+            jsonFieldNames = jsonFieldNames1;
+            getValue = getValue1;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonObject jsonHost) {
+            return jsonFieldNames
+                    .stream()
+                    .allMatch(s -> checkField(jsonHost, s, getValue.apply(vnetEntity, s)));
+        }
+
+        private boolean checkField(JsonObject jsonHost, String jsonFieldName,
+                                   String objectValue) {
+            final String jsonValue = jsonHost.get(jsonFieldName).asString();
+            if (!jsonValue.equals(objectValue)) {
+                reason = jsonFieldName + " " + objectValue;
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual network id array matcher.
+     *
+     * @param obj virtual network id object we are looking for
+     * @return matcher
+     */
+    /**
+     * Factory to allocate a virtual network entity matcher.
+     *
+     * @param obj virtual network object we are looking for
+     * @param jsonFieldNames JSON field names to check against
+     * @param getValue function to retrieve value from virtual network object
+     * @param <T>
+     * @return JsonObjectMatcher
+     */
+    private static <T> JsonObjectMatcher matchesVnetEntity(T obj, List<String> jsonFieldNames,
+                                                    BiFunction<T, String, String> getValue) {
+        return new JsonObjectMatcher(obj, jsonFieldNames, getValue);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual network entity is represented properly in a JSON
+     * array of virtual network entities.
+     */
+    public static class JsonArrayMatcher<T> extends TypeSafeMatcher<JsonArray> {
+        private final T vnetEntity;
+        private String reason = "";
+        private Function<T, String> getKey; // gets vnetEntity's key
+        private BiPredicate<T, JsonObject> checkKey; // check vnetEntity's key with JSON rep'n
+        private List<String> jsonFieldNames; // field/property names
+        private BiFunction<T, String, String> getValue; // get vnetEntity's value
+
+        public JsonArrayMatcher(T vnetEntityValue, Function<T, String> getKey1,
+                                BiPredicate<T, JsonObject> checkKey1,
+                                List<String> jsonFieldNames1,
+                                BiFunction<T, String, String> getValue1) {
+            vnetEntity = vnetEntityValue;
+            getKey = getKey1;
+            checkKey = checkKey1;
+            jsonFieldNames = jsonFieldNames1;
+            getValue = getValue1;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonArray json) {
+            boolean itemFound = false;
+            final int expectedAttributes = jsonFieldNames.size();
+            for (int jsonArrayIndex = 0; jsonArrayIndex < json.size();
+                 jsonArrayIndex++) {
+
+                final JsonObject jsonHost = json.get(jsonArrayIndex).asObject();
+
+                if (jsonHost.names().size() < expectedAttributes) {
+                    reason = "Found a virtual network with the wrong number of attributes";
+                    return false;
+                }
+
+                if (checkKey != null && checkKey.test(vnetEntity, jsonHost)) {
+                    itemFound = true;
+                    assertThat(jsonHost, matchesVnetEntity(vnetEntity, jsonFieldNames, getValue));
+                }
+            }
+            if (!itemFound) {
+                reason = getKey.apply(vnetEntity) + " was not found";
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Array matcher for VirtualNetwork.
+     */
+    public static class VnetJsonArrayMatcher extends JsonArrayMatcher<VirtualNetwork> {
+
+        public VnetJsonArrayMatcher(VirtualNetwork vnetIn) {
+            super(vnetIn,
+                  vnet -> "Virtual network " + vnet.id().toString(),
+                  (vnet, jsonObject) -> {
+                      return jsonObject.get(ID).asString().equals(vnet.id().toString()); },
+                  ImmutableList.of(ID, TENANT_ID),
+                  (vnet, s) -> {
+                      return s.equals(ID) ? vnet.id().toString()
+                              : s.equals(TENANT_ID) ? vnet.tenantId().toString()
+                              : null;
+                      }
+            );
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual network array matcher.
+     *
+     * @param vnet virtual network object we are looking for
+     * @return matcher
+     */
+    private VnetJsonArrayMatcher hasVnet(VirtualNetwork vnet) {
+        return new VnetJsonArrayMatcher(vnet);
+    }
+
+    // Tests for Virtual Networks
+
+    /**
+     * Tests the result of the REST API GET when there are no virtual networks.
+     */
+    @Test
+    public void testGetVirtualNetworksEmptyArray() {
+        expect(mockVnetAdminService.getTenantIds()).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetAdminService);
+        expect(mockVnetService.getVirtualNetworks(tenantId4)).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String response = wt.path("vnets").request().get(String.class);
+        assertThat(response, is("{\"vnets\":[]}"));
+
+        verify(mockVnetService);
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests the result of the REST API GET when virtual networks are defined.
+     */
+    @Test
+    public void testGetVirtualNetworksArray() {
+        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);
+
+        WebTarget wt = target();
+        String response = wt.path("vnets").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 adding of new virtual network using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualNetwork() {
+        expect(mockVnetAdminService.createVirtualNetwork(tenantId2)).andReturn(vnet1);
+        expectLastCall();
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = TenantWebResourceTest.class
+                .getResourceAsStream("post-tenant.json");
+
+        Response response = wt.path("vnets").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("/vnets/" + vnet1.id().toString()));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests adding of a null virtual network using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualNetworkNullTenantId() {
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        try {
+            String response = wt.path("vnets")
+                    .request(MediaType.APPLICATION_JSON_TYPE)
+                    .post(Entity.json(null), String.class);
+            fail("POST of null virtual network did not throw an exception");
+        } catch (BadRequestException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 400 Bad Request"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a virtual network with DELETE request.
+     */
+    @Test
+    public void testDeleteVirtualNetwork() {
+        mockVnetAdminService.removeVirtualNetwork(anyObject());
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        Response response = wt.path("vnets/" + "2")
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests that a DELETE of a non-existent virtual network throws an exception.
+     */
+    @Test
+    public void testDeleteNetworkNonExistentNetworkId() {
+        expect(mockVnetAdminService.getTenantIds())
+                .andReturn(ImmutableSet.of())
+                .anyTimes();
+        expectLastCall();
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+
+        try {
+            wt.path("vnets/" + "NON_EXISTENT_NETWORK_ID")
+                    .request()
+                    .delete(String.class);
+            fail("Delete of a non-existent virtual network did not throw an exception");
+        } catch (NotFoundException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 404 Not Found"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    // Tests for Virtual Device
+
+    /**
+     * Tests the result of the REST API GET when there are no virtual devices.
+     */
+    @Test
+    public void testGetVirtualDevicesEmptyArray() {
+        NetworkId networkId = networkId4;
+        expect(mockVnetService.getVirtualDevices(networkId)).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString() + "/devices";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, is("{\"devices\":[]}"));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Tests the result of the REST API GET when virtual devices are defined.
+     */
+    @Test
+    public void testGetVirtualDevicesArray() {
+        NetworkId networkId = networkId3;
+        vdevSet.add(vdev1);
+        vdevSet.add(vdev2);
+        expect(mockVnetService.getVirtualDevices(networkId)).andReturn(vdevSet).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString() + "/devices";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, containsString("{\"devices\":["));
+
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("devices"));
+
+        final JsonArray vnetJsonArray = result.get("devices").asArray();
+        assertThat(vnetJsonArray, notNullValue());
+        assertEquals("Virtual devices array is not the correct size.",
+                     vdevSet.size(), vnetJsonArray.size());
+
+        vdevSet.forEach(vdev -> assertThat(vnetJsonArray, hasVdev(vdev)));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Array matcher for VirtualDevice.
+     */
+    public static class VdevJsonArrayMatcher extends JsonArrayMatcher<VirtualDevice> {
+
+        public VdevJsonArrayMatcher(VirtualDevice vdevIn) {
+            super(vdevIn,
+                  vdev -> "Virtual device " + vdev.networkId().toString()
+                          + " " + vdev.id().toString(),
+                  (vdev, jsonObject) -> {
+                      return jsonObject.get(ID).asString().equals(vdev.networkId().toString())
+                              && jsonObject.get(DEVICE_ID).asString().equals(vdev.id().toString()); },
+                  ImmutableList.of(ID, DEVICE_ID),
+                  (vdev, s) -> {
+                      return s.equals(ID) ? vdev.networkId().toString()
+                              : s.equals(DEVICE_ID) ? vdev.id().toString()
+                              : null;
+                  }
+            );
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual device array matcher.
+     *
+     * @param vdev virtual device object we are looking for
+     * @return matcher
+     */
+    private VdevJsonArrayMatcher hasVdev(VirtualDevice vdev) {
+        return new VdevJsonArrayMatcher(vdev);
+    }
+    /**
+     * Tests adding of new virtual device using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualDevice() {
+        NetworkId networkId = networkId3;
+        DeviceId deviceId = devId2;
+        expect(mockVnetAdminService.createVirtualDevice(networkId, deviceId)).andReturn(vdev2);
+        expectLastCall();
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-device.json");
+        String reqLocation = "vnets/" + networkId.toString() + "/devices";
+        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 + "/" + vdev2.id().toString()));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests adding of a null virtual device using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualDeviceNullJsonStream() {
+        NetworkId networkId = networkId3;
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        try {
+            String reqLocation = "vnets/" + networkId.toString() + "/devices";
+            String response = wt.path(reqLocation)
+                    .request(MediaType.APPLICATION_JSON_TYPE)
+                    .post(Entity.json(null), String.class);
+            fail("POST of null virtual device did not throw an exception");
+        } catch (BadRequestException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 400 Bad Request"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a virtual device with DELETE request.
+     */
+    @Test
+    public void testDeleteVirtualDevice() {
+        NetworkId networkId = networkId3;
+        DeviceId deviceId = devId2;
+        mockVnetAdminService.removeVirtualDevice(networkId, deviceId);
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        String reqLocation = "vnets/" + networkId.toString() + "/devices/" + deviceId.toString();
+        Response response = wt.path(reqLocation)
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+
+        verify(mockVnetAdminService);
+    }
+
+    // Tests for Virtual Ports
+
+    /**
+     * Tests the result of the REST API GET when there are no virtual ports.
+     */
+    @Test
+    public void testGetVirtualPortsEmptyArray() {
+        NetworkId networkId = networkId4;
+        DeviceId deviceId = devId2;
+        expect(mockVnetService.getVirtualPorts(networkId, deviceId))
+                .andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString()
+                + "/devices/" + deviceId.toString() + "/ports";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, is("{\"ports\":[]}"));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Tests the result of the REST API GET when virtual ports are defined.
+     */
+    @Test
+    public void testGetVirtualPortsArray() {
+        NetworkId networkId = networkId3;
+        DeviceId deviceId = dev22.id();
+        vportSet.add(vport23);
+        vportSet.add(vport22);
+        expect(mockVnetService.getVirtualPorts(networkId, deviceId)).andReturn(vportSet).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString()
+                + "/devices/" + deviceId.toString() + "/ports";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, containsString("{\"ports\":["));
+
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("ports"));
+
+        final JsonArray vnetJsonArray = result.get("ports").asArray();
+        assertThat(vnetJsonArray, notNullValue());
+        assertEquals("Virtual ports array is not the correct size.",
+                vportSet.size(), vnetJsonArray.size());
+
+        vportSet.forEach(vport -> assertThat(vnetJsonArray, hasVport(vport)));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Array matcher for VirtualPort.
+     */
+    public static class VportJsonArrayMatcher extends JsonArrayMatcher<VirtualPort> {
+
+        public VportJsonArrayMatcher(VirtualPort vportIn) {
+            super(vportIn,
+                  vport -> "Virtual port " + vport.networkId().toString() + " "
+                    + vport.element().id().toString() + " " + vport.number().toString(),
+                  (vport, jsonObject) -> {
+                      return jsonObject.get(ID).asString().equals(vport.networkId().toString())
+                              && jsonObject.get(PORT_NUM).asString().equals(vport.number().toString())
+                              && jsonObject.get(DEVICE_ID).asString().equals(vport.element().id().toString()); },
+                  ImmutableList.of(ID, DEVICE_ID, PORT_NUM, PHYS_DEVICE_ID, PHYS_PORT_NUM),
+                  (vport, s) -> {
+                      return s.equals(ID) ? vport.networkId().toString()
+                              : s.equals(DEVICE_ID) ? vport.element().id().toString()
+                              : s.equals(PORT_NUM) ? vport.number().toString()
+                              : s.equals(PHYS_DEVICE_ID) ? vport.realizedBy().element().id().toString()
+                              : s.equals(PHYS_PORT_NUM) ? vport.realizedBy().number().toString()
+                              : null;
+                  }
+            );
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual port array matcher.
+     *
+     * @param vport virtual port object we are looking for
+     * @return matcher
+     */
+    private VportJsonArrayMatcher hasVport(VirtualPort vport) {
+        return new VportJsonArrayMatcher(vport);
+    }
+
+    /**
+     * Tests adding of new virtual port using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualPort() {
+        NetworkId networkId = networkId3;
+        DeviceId deviceId = devId22;
+        DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+        Device physDevice = new DefaultDevice(null, DeviceId.deviceId("dev1"),
+                null, null, null, null, null, null, annotations);
+        Port port1 = new DefaultPort(physDevice, portNumber(1), true);
+        expect(mockVnetAdminService.createVirtualPort(networkId, deviceId, portNumber(22), port1))
+                .andReturn(vport22);
+
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-port.json");
+        String reqLocation = "vnets/" + networkId.toString()
+                + "/devices/" + deviceId.toString() + "/ports";
+        Response response = wt.path(reqLocation).request(MediaType.APPLICATION_JSON_TYPE)
+                .post(Entity.json(jsonStream));
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests adding of a null virtual port using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualPortNullJsonStream() {
+        NetworkId networkId = networkId3;
+        DeviceId deviceId = devId2;
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        try {
+            String reqLocation = "vnets/" + networkId.toString()
+                    + "/devices/" + deviceId.toString() + "/ports";
+            String response = wt.path(reqLocation)
+                    .request(MediaType.APPLICATION_JSON_TYPE)
+                    .post(Entity.json(null), String.class);
+            fail("POST of null virtual port did not throw an exception");
+        } catch (BadRequestException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 400 Bad Request"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a virtual port with DELETE request.
+     */
+    @Test
+    public void testDeleteVirtualPort() {
+        NetworkId networkId = networkId3;
+        DeviceId deviceId = devId2;
+        PortNumber portNum = portNumber(2);
+        mockVnetAdminService.removeVirtualPort(networkId, deviceId, portNum);
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        String reqLocation = "vnets/" + networkId.toString()
+            + "/devices/" + deviceId.toString() + "/ports/" + portNum.toLong();
+        Response response = wt.path(reqLocation)
+                .request(MediaType.APPLICATION_JSON_TYPE)
+                .delete();
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
+
+        verify(mockVnetAdminService);
+    }
+
+    // TODO Tests for Virtual Links
+}
diff --git a/web/api/src/test/resources/org/onosproject/rest/resources/post-tenant.json b/web/api/src/test/resources/org/onosproject/rest/resources/post-tenant.json
new file mode 100644
index 0000000..407f9a2
--- /dev/null
+++ b/web/api/src/test/resources/org/onosproject/rest/resources/post-tenant.json
@@ -0,0 +1,3 @@
+{
+  "id": "TenantId2"
+}
diff --git a/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-device.json b/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-device.json
new file mode 100644
index 0000000..fbd129c
--- /dev/null
+++ b/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-device.json
@@ -0,0 +1,4 @@
+{
+  "networkId": "3",
+  "deviceId": "devId2"
+}
diff --git a/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-port.json b/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-port.json
new file mode 100644
index 0000000..1d60842
--- /dev/null
+++ b/web/api/src/test/resources/org/onosproject/rest/resources/post-virtual-port.json
@@ -0,0 +1,7 @@
+{
+  "networkId": "3",
+  "deviceId": "dev22",
+  "portNum": "22",
+  "physDeviceId": "dev1",
+  "physPortNum": "1"
+}