Detangling incubator: virtual nets, tunnels, resource labels, oh my

- virtual networking moved to /apps/virtual; with CLI & REST API
- tunnels and labels moved to /apps/tunnel; with CLI & REST API; UI disabled for now
- protobuf/models moved to /core/protobuf/models
- defunct grpc/rpc registry stuff left under /graveyard
- compile dependencies on /incubator moved to respective modules for compilation
- run-time dependencies will need to be re-tested for dependent apps

- /graveyard will be removed in not-too-distant future

Change-Id: I0a0b995c635487edcf95a352f50dd162186b0b39
diff --git a/apps/virtual/BUILD b/apps/virtual/BUILD
new file mode 100644
index 0000000..1f3cb57
--- /dev/null
+++ b/apps/virtual/BUILD
@@ -0,0 +1,13 @@
+BUNDLES = [
+    "//apps/virtual/api:onos-apps-virtual-api",
+    "//apps/virtual/app:onos-apps-virtual-app",
+]
+
+onos_app(
+    app_name = "org.onosproject.virtual",
+    category = "Traffic Engineering",
+    description = "Virtual Network Subsystem extension",
+    included_bundles = BUNDLES,
+    title = "Virtual Network Subsystem",
+    url = "http://onosproject.org",
+)
diff --git a/apps/virtual/api/BUILD b/apps/virtual/api/BUILD
new file mode 100644
index 0000000..da7ca40
--- /dev/null
+++ b/apps/virtual/api/BUILD
@@ -0,0 +1,11 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + [
+    "//apps/tunnel/api:onos-apps-tunnel-api",
+]
+
+TEST_DEPS = TEST_ADAPTERS
+
+osgi_jar_with_tests(
+    test_deps = TEST_DEPS,
+    visibility = ["//visibility:public"],
+    deps = COMPILE_DEPS,
+)
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/AbstractVnetService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/AbstractVnetService.java
new file mode 100644
index 0000000..b3e1d18
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/AbstractVnetService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onlab.osgi.ServiceDirectory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Basis for virtual network service.
+ */
+public abstract class AbstractVnetService
+        implements VnetService {
+
+    private static final String NETWORK_NULL = "Network ID cannot be null";
+
+    protected NetworkId networkId;
+    protected VirtualNetworkService manager;
+    protected ServiceDirectory serviceDirectory;
+
+    public AbstractVnetService(VirtualNetworkService manager,
+                               NetworkId networkId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        this.manager = manager;
+        this.networkId = networkId;
+        this.serviceDirectory = manager.getServiceDirectory();
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return this.networkId;
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/Comparators.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/Comparators.java
new file mode 100644
index 0000000..8c67e00
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/Comparators.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import java.util.Comparator;
+
+/**
+ * Various comparators.
+ */
+public final class Comparators {
+
+    // Ban construction
+    private Comparators() {
+    }
+
+    public static final Comparator<VirtualNetwork> VIRTUAL_NETWORK_COMPARATOR =
+            (v1, v2) -> {
+                int compareId = v1.tenantId().toString().compareTo(v2.tenantId().toString());
+                return (compareId != 0) ? compareId : Long.signum(v1.id().id() - v2.id().id());
+            };
+
+    public static final Comparator<VirtualDevice> VIRTUAL_DEVICE_COMPARATOR =
+            (v1, v2) -> v1.id().toString().compareTo(v2.id().toString());
+
+    public static final Comparator<VirtualPort> VIRTUAL_PORT_COMPARATOR =
+            (v1, v2) -> v1.number().toString().compareTo(v2.number().toString());
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualDevice.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualDevice.java
new file mode 100644
index 0000000..98e3e1e
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualDevice.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onlab.packet.ChassisId;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.*;
+
+/**
+ * Default representation of a virtual device.
+ */
+public final class DefaultVirtualDevice extends DefaultDevice implements VirtualDevice {
+
+    private static final String VIRTUAL = "virtual";
+    private static final ProviderId PID = new ProviderId(VIRTUAL, VIRTUAL);
+
+    private final NetworkId networkId;
+
+    /**
+     * Creates a network element attributed to the specified provider.
+     *
+     * @param networkId network identifier
+     * @param id        device identifier
+     */
+    public DefaultVirtualDevice(NetworkId networkId, DeviceId id) {
+        super(PID, id, Type.VIRTUAL, VIRTUAL, VIRTUAL, VIRTUAL, VIRTUAL,
+              new ChassisId(0));
+        this.networkId = networkId;
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultVirtualDevice) {
+            DefaultVirtualDevice that = (DefaultVirtualDevice) obj;
+            return super.equals(that) && Objects.equals(this.networkId, that.networkId);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("networkId", networkId).toString();
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualHost.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualHost.java
new file mode 100644
index 0000000..61f31b1
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualHost.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default representation of a virtual host.
+ */
+public final class DefaultVirtualHost extends DefaultHost implements VirtualHost {
+
+    private static final String VIRTUAL = "virtual";
+    private static final ProviderId PID = new ProviderId(VIRTUAL, VIRTUAL);
+
+    private final NetworkId networkId;
+
+    /**
+     * Creates a virtual host attributed to the specified provider.
+     *
+     * @param networkId network identifier
+     * @param id        host identifier
+     * @param mac       host MAC address
+     * @param vlan      host VLAN identifier
+     * @param location  host location
+     * @param ips       host IP addresses
+     */
+    public DefaultVirtualHost(NetworkId networkId, HostId id, MacAddress mac,
+                              VlanId vlan, HostLocation location, Set<IpAddress> ips) {
+        this(networkId, id, mac, vlan, Collections.singleton(location), ips);
+    }
+
+    /**
+     * Creates a virtual host attributed to the specified provider.
+     *
+     * @param networkId network identifier
+     * @param id        host identifier
+     * @param mac       host MAC address
+     * @param vlan      host VLAN identifier
+     * @param locations host locations
+     * @param ips       host IP addresses
+     */
+    public DefaultVirtualHost(NetworkId networkId, HostId id, MacAddress mac,
+                              VlanId vlan, Set<HostLocation> locations, Set<IpAddress> ips) {
+        super(PID, id, mac, vlan, locations, ips, false, DefaultAnnotations.builder().build());
+        this.networkId = networkId;
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultVirtualHost) {
+            DefaultVirtualHost that = (DefaultVirtualHost) obj;
+            return super.equals(that) && Objects.equals(this.networkId, that.networkId);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("networkId", networkId).toString();
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualLink.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualLink.java
new file mode 100644
index 0000000..20924bc
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualLink.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default representation of a virtual link.
+ */
+public final class DefaultVirtualLink extends DefaultLink implements VirtualLink {
+
+    private static final String VIRTUAL = "virtualLink";
+    public static final ProviderId PID = new ProviderId(VIRTUAL, VIRTUAL);
+
+    private final NetworkId networkId;
+    private final TunnelId tunnelId;
+
+    /**
+     * Private constructor for a default virtual link.
+     *
+     * @param networkId network identifier
+     * @param src       source connection point
+     * @param dst       destination connection point
+     * @param state     link state
+     * @param tunnelId  tunnel identifier
+     */
+    private DefaultVirtualLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst,
+                               State state, TunnelId tunnelId) {
+        super(PID, src, dst, Type.VIRTUAL, state, DefaultAnnotations.builder().build());
+        this.networkId = networkId;
+        this.tunnelId = tunnelId;
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    /**
+     * Returns the tunnel identifier.
+     *
+     * @return tunnel identifier.
+     */
+    public TunnelId tunnelId() {
+        return tunnelId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, tunnelId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultVirtualLink) {
+            DefaultVirtualLink that = (DefaultVirtualLink) obj;
+            return super.equals(that) &&
+                    Objects.equals(this.networkId, that.networkId) &&
+                    Objects.equals(this.tunnelId, that.tunnelId);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("networkId", networkId).add("tunnelId", tunnelId).toString();
+    }
+
+    /**
+     * Creates a new default virtual link builder.
+     *
+     * @return default virtual link builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for DefaultVirtualLink objects.
+     */
+    public static final class Builder extends DefaultLink.Builder {
+        private NetworkId networkId;
+        private ConnectPoint src;
+        private ConnectPoint dst;
+        private TunnelId tunnelId;
+        private State state;
+
+        private Builder() {
+            // Hide constructor
+        }
+
+        /**
+         * Sets the network identifier to be used by the builder.
+         *
+         * @param networkId network identifier
+         * @return self
+         */
+        public Builder networkId(NetworkId networkId) {
+            this.networkId = networkId;
+            return this;
+        }
+
+        /**
+         * Sets the source connect point to be used by the builder.
+         *
+         * @param src source connect point
+         * @return self
+         */
+        public Builder src(ConnectPoint src) {
+            this.src = src;
+            return this;
+        }
+
+        /**
+         * Sets the destination connect point to be used by the builder.
+         *
+         * @param dst new destination connect point
+         * @return self
+         */
+        public Builder dst(ConnectPoint dst) {
+            this.dst = dst;
+            return this;
+        }
+
+        /**
+         * Sets the tunnel identifier to be used by the builder.
+         *
+         * @param tunnelId tunnel identifier
+         * @return self
+         */
+        public Builder tunnelId(TunnelId tunnelId) {
+            this.tunnelId = tunnelId;
+            return this;
+        }
+
+        /**
+         * Sets the link state to be used by the builder.
+         *
+         * @param state link state
+         * @return self
+         */
+        public Builder state(State state) {
+            this.state = state;
+            return this;
+        }
+
+        /**
+         * Builds a default virtual link object from the accumulated parameters.
+         *
+         * @return default virtual link object
+         */
+        public DefaultVirtualLink build() {
+            checkNotNull(src, "Source connect point cannot be null");
+            checkNotNull(dst, "Destination connect point cannot be null");
+            checkNotNull(networkId, "Network Id cannot be null");
+
+            return new DefaultVirtualLink(networkId, src, dst, state, tunnelId);
+        }
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualNetwork.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualNetwork.java
new file mode 100644
index 0000000..bf7de57
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualNetwork.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.TenantId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default implementation of the virtual network descriptor.
+ */
+public final class DefaultVirtualNetwork implements VirtualNetwork {
+
+    private final NetworkId id;
+    private final TenantId tenantId;
+
+    /**
+     * Creates a new virtual network descriptor.
+     *
+     * @param id       network identifier
+     * @param tenantId tenant identifier
+     */
+    public DefaultVirtualNetwork(NetworkId id, TenantId tenantId) {
+        this.id = id;
+        this.tenantId = tenantId;
+    }
+
+    @Override
+    public NetworkId id() {
+        return id;
+    }
+
+    @Override
+    public TenantId tenantId() {
+        return tenantId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, tenantId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultVirtualNetwork) {
+            DefaultVirtualNetwork that = (DefaultVirtualNetwork) obj;
+            return Objects.equals(this.id, that.id)
+                    && Objects.equals(this.tenantId, that.tenantId);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("id", id)
+                .add("tenantId", tenantId)
+                .toString();
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualPort.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualPort.java
new file mode 100644
index 0000000..d5f3efb
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/DefaultVirtualPort.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.Element;
+import org.onosproject.net.PortNumber;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Default representation of a virtual port.
+ */
+public final class DefaultVirtualPort extends DefaultPort implements VirtualPort {
+
+    private final NetworkId networkId;
+    private final ConnectPoint realizedBy;
+
+    /**
+     * Creates a virtual port.
+     *
+     * @param networkId  network identifier
+     * @param device     parent network element
+     * @param portNumber port number
+     * @param realizedBy underling port which realizes this virtual port
+     */
+    public DefaultVirtualPort(NetworkId networkId, Device device, PortNumber portNumber,
+                              ConnectPoint realizedBy) {
+        this(networkId, device, portNumber, false, realizedBy);
+    }
+
+    /**
+     * Creates a virtual port.
+     *
+     * @param networkId  network identifier
+     * @param device     parent network element
+     * @param portNumber port number
+     * @param isEnabled  indicator whether the port is up and active
+     * @param realizedBy underling port which realizes this virtual port
+     */
+    public DefaultVirtualPort(NetworkId networkId, Device device, PortNumber portNumber,
+                              boolean isEnabled, ConnectPoint realizedBy) {
+        super((Element) device, portNumber, isEnabled, DefaultAnnotations.builder().build());
+        this.networkId = networkId;
+        this.realizedBy = realizedBy;
+    }
+
+    /**
+     * Returns network identifier.
+     *
+     * @return network identifier
+     */
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    @Override
+    public ConnectPoint realizedBy() {
+        return realizedBy;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, realizedBy);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultVirtualPort) {
+            DefaultVirtualPort that = (DefaultVirtualPort) obj;
+            return super.equals(that) &&
+                    Objects.equals(this.networkId, that.networkId) &&
+                    Objects.equals(this.number(), that.number()) &&
+                    Objects.equals(this.realizedBy, that.realizedBy);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("networkId", networkId).add("realizedBy", realizedBy).toString();
+    }
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/NetworkId.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/NetworkId.java
new file mode 100644
index 0000000..552e941
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/NetworkId.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+import java.util.Objects;
+
+/**
+ * Representation of network identity.
+ */
+@Beta
+public final class NetworkId extends Identifier<Long> {
+
+    /**
+     * Represents no network, or an unspecified network.
+     */
+    public static final NetworkId NONE = networkId(-1L);
+
+    /**
+     * Represents the underlying physical network.
+     */
+    public static final NetworkId PHYSICAL = networkId(0L);
+
+    /**
+     * Checks if the id is for virtual network.
+     *
+     * @return true if the id is for virtual network.
+     */
+    public final boolean isVirtualNetworkId() {
+        return (!Objects.equals(this, NONE) && !Objects.equals(this, PHYSICAL));
+    }
+
+    // Public construction is prohibited
+    private NetworkId(long id) {
+        super(id);
+    }
+
+
+    // Default constructor for serialization
+    protected NetworkId() {
+        super(-1L);
+    }
+
+    /**
+     * Creates a network id using the supplied backing id.
+     *
+     * @param id network id
+     * @return network identifier
+     */
+    public static NetworkId networkId(long id) {
+        return new NetworkId(id);
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualDevice.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualDevice.java
new file mode 100644
index 0000000..d4b098f
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualDevice.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Device;
+
+/**
+ * Abstraction of a virtual device.
+ */
+@Beta
+public interface VirtualDevice extends VirtualElement, Device {
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualElement.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualElement.java
new file mode 100644
index 0000000..572d02d
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualElement.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of a virtual element.
+ */
+@Beta
+public interface VirtualElement {
+
+    /**
+     * Returns the network identifier to which this virtual element belongs.
+     *
+     * @return network identifier
+     */
+    NetworkId networkId();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualHost.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualHost.java
new file mode 100644
index 0000000..569d8e3
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualHost.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.Host;
+
+/**
+ * Abstraction of a virtual end-station host.
+ */
+@Beta
+public interface VirtualHost extends VirtualElement, Host {
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualLink.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualLink.java
new file mode 100644
index 0000000..6f68961
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualLink.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.net.Link;
+
+/**
+ * Abstraction of a virtual link.
+ */
+@Beta
+public interface VirtualLink extends VirtualElement, Link {
+    /**
+     * Returns the tunnel identifier to which this virtual link belongs.
+     *
+     * @return tunnel identifier
+     */
+    TunnelId tunnelId();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetwork.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetwork.java
new file mode 100644
index 0000000..1cbbddb
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetwork.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.TenantId;
+
+/**
+ * Representation of a virtual network.
+ */
+@Beta
+public interface VirtualNetwork {
+
+    /**
+     * Returns the network identifier.
+     *
+     * @return network id
+     */
+    NetworkId id();
+
+    /**
+     * Returns the identifier of the tenant to which this virtual network belongs.
+     *
+     * @return tenant identifier
+     */
+    TenantId tenantId();
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkAdminService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkAdminService.java
new file mode 100644
index 0000000..bade4078
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkAdminService.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TenantId;
+
+import java.util.Set;
+
+/**
+ * Service for managing the inventory of virtual networks.
+ */
+@Beta
+public interface VirtualNetworkAdminService extends VirtualNetworkService {
+
+    /**
+     * Registers the specified, externally generated tenant identifier.
+     *
+     * @param tenantId tenant identifier
+     */
+    void registerTenantId(TenantId tenantId);
+
+    /**
+     * Unregisters the specified, externally generated tenant identifier.
+     *
+     * @param tenantId tenant identifier
+     * @throws IllegalStateException if there are networks still owned by this tenant
+     */
+    void unregisterTenantId(TenantId tenantId);
+
+    /**
+     * Returns the set of tenant identifiers known to the system.
+     *
+     * @return set of known tenant identifiers
+     */
+    Set<TenantId> getTenantIds();
+
+    /**
+     * Creates a new virtual network for the specified tenant.
+     *
+     * @param tenantId tenant identifier
+     * @return newly created virtual network
+     */
+    VirtualNetwork createVirtualNetwork(TenantId tenantId);
+
+    /**
+     * Removes the specified virtual network and all its devices and links.
+     *
+     * @param networkId network identifier
+     */
+    void removeVirtualNetwork(NetworkId networkId);
+
+    /**
+     * Creates a new virtual device within the specified network. The device id
+     * must be unique within the bounds of the network.
+     *
+     * @param networkId network identifier
+     * @param deviceId  device identifier
+     * @return newly created virtual device
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    VirtualDevice createVirtualDevice(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Removes the specified virtual device and all its ports and affiliated links.
+     *
+     * @param networkId network identifier
+     * @param deviceId  device identifier
+     * @throws org.onlab.util.ItemNotFoundException if no such network or device found
+     */
+    void removeVirtualDevice(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Creates a new virtual host within the specified network. The host id
+     * must be unique within the bounds of the network.
+     *
+     * @param networkId network identifier
+     * @param hostId    host identifier
+     * @param mac       mac address
+     * @param vlan      vlan identifier
+     * @param location  host location
+     * @param ips       set of ip addresses
+     * @return newly created virtual host
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    VirtualHost createVirtualHost(NetworkId networkId, HostId hostId, MacAddress mac,
+                                  VlanId vlan, HostLocation location, Set<IpAddress> ips);
+
+    /**
+     * Removes the specified virtual host.
+     *
+     * @param networkId network identifier
+     * @param hostId  host identifier
+     * @throws org.onlab.util.ItemNotFoundException if no such network or host found
+     */
+    void removeVirtualHost(NetworkId networkId, HostId hostId);
+
+    /**
+     * Creates a new virtual link within the specified network.
+     *
+     * @param networkId  network identifier
+     * @param src        source connection point
+     * @param dst        destination connection point
+     * @return newly created virtual link
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    VirtualLink createVirtualLink(NetworkId networkId,
+                                  ConnectPoint src, ConnectPoint dst);
+
+    // TODO: Discuss whether we should provide an alternate createVirtualLink
+    // which is backed by a Path instead; I'm leaning towards not doing that.
+
+    /**
+     * Removes the specified virtual link.
+     *
+     * @param networkId network identifier
+     * @param src       source connection point
+     * @param dst       destination connection point
+     * @throws org.onlab.util.ItemNotFoundException if no such network or link found
+     */
+    void removeVirtualLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst);
+
+    /**
+     * Creates a new virtual port on the specified device.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   virtual device identifier
+     * @param portNumber virtual port number
+     * @param realizedBy underlying physical port using which this virtual port is realized
+     * @return newly created port
+     * @throws org.onlab.util.ItemNotFoundException if no such network or device is found
+     */
+    VirtualPort createVirtualPort(NetworkId networkId, DeviceId deviceId,
+                                  PortNumber portNumber, ConnectPoint realizedBy);
+
+    /**
+     * Binds an existing virtual port on the specified device.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   virtual device identifier
+     * @param portNumber virtual port number
+     * @param realizedBy underlying physical port using which this virtual port is realized
+     * @throws org.onlab.util.ItemNotFoundException if no such network or device is found
+     */
+    void bindVirtualPort(NetworkId networkId, DeviceId deviceId,
+                                  PortNumber portNumber, ConnectPoint realizedBy);
+
+    /**
+     * Updates port state of an existing virtual port on the specified device.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   virtual device identifier
+     * @param portNumber virtual port number
+     * @param isEnabled  indicator whether the port is up and active
+     * @throws org.onlab.util.ItemNotFoundException if no such network or device is found
+     */
+    void updatePortState(NetworkId networkId, DeviceId deviceId,
+                                  PortNumber portNumber, boolean isEnabled);
+
+    /**
+     * Removes the specified virtual port.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   device identifier
+     * @param portNumber port number
+     * @throws org.onlab.util.ItemNotFoundException if no such network or port found
+     */
+    void removeVirtualPort(NetworkId networkId, DeviceId deviceId, PortNumber portNumber);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkEvent.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkEvent.java
new file mode 100644
index 0000000..3f96496
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkEvent.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Describes virtual network event.
+ */
+public class VirtualNetworkEvent extends AbstractEvent<VirtualNetworkEvent.Type, NetworkId> {
+
+    /**
+     * Type of virtual network events.
+     */
+    public enum Type {
+        /**
+         * Signifies that a new tenant identifier was registered.
+         */
+        TENANT_REGISTERED,
+        /**
+         * Signifies that a tenant identifier was unregistered.
+         */
+        TENANT_UNREGISTERED,
+        /**
+         * Signifies that a new virtual network was added.
+         */
+        NETWORK_ADDED,
+        /**
+         * Signifies that a virtual network was updated.
+         */
+        NETWORK_UPDATED,
+        /**
+         * Signifies that a virtual network was removed.
+         */
+        NETWORK_REMOVED,
+        /**
+         * Signifies that a new virtual network device was added.
+         */
+        VIRTUAL_DEVICE_ADDED,
+        /**
+         * Signifies that a virtual network device was updated.
+         */
+        VIRTUAL_DEVICE_UPDATED,
+        /**
+         * Signifies that a virtual network device was removed.
+         */
+        VIRTUAL_DEVICE_REMOVED,
+        /**
+         * Signifies that a new virtual network port was added.
+         */
+        VIRTUAL_PORT_ADDED,
+        /**
+         * Signifies that a virtual network port was updated.
+         */
+        VIRTUAL_PORT_UPDATED,
+        /**
+         * Signifies that a virtual network port was removed.
+         */
+        VIRTUAL_PORT_REMOVED
+    }
+
+    private final VirtualDevice virtualDevice;
+    private final VirtualPort virtualPort;
+
+    /**
+     * Creates an event of a given type and for the specified subject.
+     *
+     * @param type        event type
+     * @param subject     event subject
+     */
+    public VirtualNetworkEvent(Type type, NetworkId subject) {
+        this(type, subject, null, null);
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject and the
+     * virtual device.
+     *
+     * @param type          event type
+     * @param subject       event subject
+     * @param virtualDevice virtual device
+     */
+    public VirtualNetworkEvent(Type type, NetworkId subject, VirtualDevice virtualDevice) {
+        this(type, subject, virtualDevice, null);
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject, virtual device and
+     * virtual port.
+     *
+     * @param type          event type
+     * @param subject       event subject
+     * @param virtualDevice virtual device
+     * @param virtualPort   virtual port
+     */
+    public VirtualNetworkEvent(Type type, NetworkId subject, VirtualDevice virtualDevice,
+                                VirtualPort virtualPort) {
+        super(type, subject);
+        this.virtualDevice = virtualDevice;
+        this.virtualPort = virtualPort;
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject and time.
+     *
+     * @param type        event type
+     * @param subject     event subject
+     * @param time        occurrence time
+     */
+    public VirtualNetworkEvent(Type type, NetworkId subject, long time) {
+        this(type, subject, null, null, time);
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject, virtual device and time.
+     *
+     * @param type          event type
+     * @param subject       event subject
+     * @param virtualDevice virtual device
+     * @param time          occurrence time
+     */
+    public VirtualNetworkEvent(Type type, NetworkId subject, VirtualDevice virtualDevice,
+                               long time) {
+        this(type, subject, virtualDevice, null, time);
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject, virtual port and time.
+     *
+     * @param type          device event type
+     * @param subject       event subject
+     * @param virtualPort   virtual port
+     * @param time          occurrence time
+     */
+    public VirtualNetworkEvent(Type type, NetworkId subject, VirtualPort virtualPort,
+                               long time) {
+        this(type, subject, null, virtualPort, time);
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject, virtual device,
+     * virtual port and time.
+     *
+     * @param type          device event type
+     * @param subject       event subject
+     * @param virtualDevice virtual device
+     * @param virtualPort   virtual port
+     * @param time          occurrence time
+     */
+    private VirtualNetworkEvent(Type type, NetworkId subject, VirtualDevice virtualDevice,
+                                VirtualPort virtualPort, long time) {
+        super(type, subject, time);
+        this.virtualDevice = virtualDevice;
+        this.virtualPort = virtualPort;
+    }
+
+    /**
+     * Returns virtual device affected by event - may be null (for events relating to
+     * tenants and virtual networks).
+     *
+     * @return virtual device
+     */
+    public VirtualDevice virtualDevice() {
+        return virtualDevice;
+    }
+
+    /**
+     * Returns virtual port affected by event - may be null (for events relating to
+     * tenants, virtual networks and virtual devices).
+     *
+     * @return virtual port
+     */
+    public VirtualPort virtualPort() {
+        return virtualPort;
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowObjectiveStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowObjectiveStore.java
new file mode 100644
index 0000000..c118958
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowObjectiveStore.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate;
+import org.onosproject.net.flowobjective.ObjectiveEvent;
+
+import java.util.Map;
+
+/**
+ * The flow objective store for virtual networks.
+ */
+public interface VirtualNetworkFlowObjectiveStore
+        extends VirtualStore<ObjectiveEvent, FlowObjectiveStoreDelegate> {
+
+    /**
+     * Adds a NextGroup to the store, by mapping it to the nextId as key,
+     * and replacing any previous mapping.
+     *
+     * @param networkId a virtual network identifier
+     * @param nextId an integer
+     * @param group a next group opaque object
+     */
+    void putNextGroup(NetworkId networkId, Integer nextId, NextGroup group);
+
+    /**
+     * Fetch a next group from the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param nextId an integer used as key
+     * @return a next group, or null if group was not found
+     */
+    NextGroup getNextGroup(NetworkId networkId, Integer nextId);
+
+    /**
+     * Remove a next group mapping from the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param nextId  the key to remove from the store.
+     * @return the next group which mapped to the nextId and is now removed, or
+     *          null if no group mapping existed in the store
+     */
+    NextGroup removeNextGroup(NetworkId networkId, Integer nextId);
+
+    /**
+     * Fetch all groups from the store and their mapping to nextIds.
+     *
+     * @param networkId a virtual network identifier
+     * @return a map that represents the current snapshot of Next-ids to NextGroups
+     */
+    Map<Integer, NextGroup> getAllGroups(NetworkId networkId);
+
+    /**
+     * Allocates a next objective id. This id is globally unique.
+     *
+     * @param networkId a virtual network identifier
+     * @return an integer
+     */
+    int allocateNextId(NetworkId networkId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java
new file mode 100644
index 0000000..6b532c3
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkFlowRuleStore.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.TableStatisticsEntry;
+
+import java.util.List;
+
+/**
+ * Manages inventory of flow rules for virtual networks;
+ * not intended for direct use.
+ */
+public interface VirtualNetworkFlowRuleStore
+        extends VirtualStore<FlowRuleBatchEvent, FlowRuleStoreDelegate> {
+    /**
+     * Returns the number of flow rule in the store.
+     *
+     * @param networkId virtual network identifier
+     * @return number of flow rules
+     */
+    int getFlowRuleCount(NetworkId networkId);
+
+    /**
+     * Returns the stored flow.
+     *
+     * @param networkId virtual network identifier
+     * @param rule the rule to look for
+     * @return a flow rule
+     */
+    FlowEntry getFlowEntry(NetworkId networkId, FlowRule rule);
+
+    /**
+     * Returns the flow entries associated with a device.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId the device ID
+     * @return the flow entries
+     */
+    Iterable<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Stores a batch of flow rules.
+     *
+     * @param networkId virtual network identifier
+     * @param batchOperation batch of flow rules.
+     *           A batch can contain flow rules for a single device only.
+     *
+     */
+    void storeBatch(NetworkId networkId, FlowRuleBatchOperation batchOperation);
+
+    /**
+     * Invoked on the completion of a storeBatch operation.
+     *
+     * @param networkId virtual network identifier
+     * @param event flow rule batch event
+     */
+    void batchOperationComplete(NetworkId networkId, FlowRuleBatchEvent event);
+
+    /**
+     * Marks a flow rule for deletion. Actual deletion will occur
+     * when the provider indicates that the flow has been removed.
+     *
+     * @param networkId virtual network identifier
+     * @param rule the flow rule to delete
+     */
+    void deleteFlowRule(NetworkId networkId, FlowRule rule);
+
+    /**
+     * Stores a new flow rule, or updates an existing entry.
+     *
+     * @param networkId virtual network identifier
+     * @param rule the flow rule to add or update
+     * @return flow_added event, or null if just an update
+     */
+    FlowRuleEvent addOrUpdateFlowRule(NetworkId networkId, FlowEntry rule);
+
+    /**
+     * Removes an existing flow entry.
+     *
+     * @param rule the flow entry to remove
+     * @param networkId virtual network identifier
+     * @return flow_removed event, or null if nothing removed
+     */
+    FlowRuleEvent removeFlowRule(NetworkId networkId, FlowEntry rule);
+
+    /**
+     * Marks a flow rule as PENDING_ADD during retry.
+     *
+     * Emits flow_update event if the state is changed
+     *
+     * @param networkId virtual network identifier
+     * @param rule the flow rule that is retrying
+     * @return flow_updated event, or null if nothing updated
+     */
+    FlowRuleEvent pendingFlowRule(NetworkId networkId, FlowEntry rule);
+
+    /**
+     * Removes all flow entries of given device from store.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId device id
+     */
+    default void purgeFlowRule(NetworkId networkId, DeviceId deviceId) {}
+
+    /**
+     * Removes all flow entries from store.
+     *
+     * @param networkId virtual network identifier
+     */
+    void purgeFlowRules(NetworkId networkId);
+
+    /**
+     * Updates the flow table statistics of the specified device using
+     * the given statistics.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId    device identifier
+     * @param tableStats   list of table statistics
+     * @return ready to send event describing what occurred;
+     */
+    FlowRuleEvent updateTableStatistics(NetworkId networkId, DeviceId deviceId,
+                                        List<TableStatisticsEntry> tableStats);
+
+    /**
+     * Returns the flow table statistics associated with a device.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId the device ID
+     * @return the flow table statistics
+     */
+    Iterable<TableStatisticsEntry> getTableStatistics(NetworkId networkId, DeviceId deviceId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkGroupStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkGroupStore.java
new file mode 100644
index 0000000..d080524
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkGroupStore.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupStoreDelegate;
+
+import java.util.Collection;
+
+/**
+ * Manages inventory of groups per virtual network and virtual device;
+ * not intended for direct use.
+ */
+public interface VirtualNetworkGroupStore
+        extends VirtualStore<GroupEvent, GroupStoreDelegate> {
+
+    enum UpdateType {
+        /**
+         * Modify existing group entry by adding provided information.
+         */
+        ADD,
+        /**
+         * Modify existing group by removing provided information from it.
+         */
+        REMOVE,
+        /**
+         * Modify existing group entry by setting the provided information,
+         * overwriting the previous group entry entirely.
+         */
+        SET
+    }
+
+    /**
+     * Returns the number of groups for the specified virtual device in the store.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @return number of groups for the specified device
+     */
+    int getGroupCount(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns the groups associated with a virtual device.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @return the group entries
+     */
+    Iterable<Group> getGroups(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns the stored group entry in a virtual network.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param appCookie the group key
+     * @return a group associated with the key
+     */
+    Group getGroup(NetworkId networkId, DeviceId deviceId, GroupKey appCookie);
+
+    /**
+     * Returns the stored group entry for an id.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param groupId the group identifier
+     * @return a group associated with the key
+     */
+    Group getGroup(NetworkId networkId, DeviceId deviceId, GroupId groupId);
+
+    /**
+     * Stores a new group entry using the information from group description
+     * for a virtual network.
+     *
+     * @param networkId the virtual network ID
+     * @param groupDesc group description to be used to store group entry
+     */
+    void storeGroupDescription(NetworkId networkId, GroupDescription groupDesc);
+
+    /**
+     * Updates the existing group entry with the information
+     * from group description.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param oldAppCookie the current group key
+     * @param type update type
+     * @param newBuckets group buckets for updates
+     * @param newAppCookie optional new group key
+     */
+    void updateGroupDescription(NetworkId networkId,
+                                DeviceId deviceId,
+                                GroupKey oldAppCookie,
+                                UpdateType type,
+                                GroupBuckets newBuckets,
+                                GroupKey newAppCookie);
+
+    /**
+     * Triggers deleting the existing group entry.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param appCookie the group key
+     */
+    void deleteGroupDescription(NetworkId networkId,
+                                DeviceId deviceId,
+                                GroupKey appCookie);
+
+    /**
+     * Stores a new group entry, or updates an existing entry.
+     *
+     * @param networkId the virtual network ID
+     * @param group group entry
+     */
+    void addOrUpdateGroupEntry(NetworkId networkId, Group group);
+
+    /**
+     * Removes the group entry from store.
+     *
+     * @param networkId the virtual network ID
+     * @param group group entry
+     */
+    void removeGroupEntry(NetworkId networkId, Group group);
+
+    /**
+     * Removes all group entries of given device from store.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId device id
+     */
+    void purgeGroupEntry(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Removes all group entries from store.
+     *
+     * @param networkId the virtual network ID
+     */
+    default void purgeGroupEntries(NetworkId networkId) {}
+
+    /**
+     * A group entry that is present in switch but not in the store.
+     *
+     * @param networkId the virtual network ID
+     * @param group group entry
+     */
+    void addOrUpdateExtraneousGroupEntry(NetworkId networkId, Group group);
+
+    /**
+     * Remove the group entry from extraneous database.
+     *
+     * @param networkId the virtual network ID
+     * @param group group entry
+     */
+    void removeExtraneousGroupEntry(NetworkId networkId, Group group);
+
+    /**
+     * Returns the extraneous groups associated with a device.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     *
+     * @return the extraneous group entries
+     */
+    Iterable<Group> getExtraneousGroups(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Indicates the first group audit is completed.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param completed initial audit status
+     */
+    void deviceInitialAuditCompleted(NetworkId networkId, DeviceId deviceId, boolean completed);
+
+    /**
+     * Retrieves the initial group audit status for a device.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     *
+     * @return initial group audit status
+     */
+    boolean deviceInitialAuditStatus(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Indicates the group operations failed.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param operation the group operation failed
+     */
+    void groupOperationFailed(NetworkId networkId, DeviceId deviceId, GroupOperation operation);
+
+    /**
+     * Submits the group metrics to store for a given device ID.
+     *
+     * @param networkId the virtual network ID
+     * @param deviceId the device ID
+     * @param groupEntries the group entries as received from southbound
+     */
+    void pushGroupMetrics(NetworkId networkId, DeviceId deviceId, Collection<Group> groupEntries);
+
+    /**
+     * Indicates failover within a failover group.
+     *
+     * @param networkId the virtual network ID
+     * @param failoverGroups groups to notify
+     */
+    void notifyOfFailovers(NetworkId networkId, Collection<Group> failoverGroups);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkIntent.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkIntent.java
new file mode 100644
index 0000000..817db21
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkIntent.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.ResourceGroup;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.ConnectivityIntent;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Key;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of VirtualNetworkIntent connectivity.
+ */
+@Beta
+public final class VirtualNetworkIntent extends ConnectivityIntent {
+
+    private final NetworkId networkId;
+    private final ConnectPoint ingressPoint;
+    private final ConnectPoint egressPoint;
+
+    private static final String NETWORK_ID_NULL = "Network ID cannot be null";
+
+    /**
+     * Returns a new point to point intent builder. The application id,
+     * ingress point and egress point are required fields.  If they are
+     * not set by calls to the appropriate methods, an exception will
+     * be thrown.
+     *
+     * @return point to point builder
+     */
+    public static VirtualNetworkIntent.Builder builder() {
+        return new VirtualNetworkIntent.Builder();
+    }
+
+    /**
+     * Builder of a point to point intent.
+     */
+    public static final class Builder extends ConnectivityIntent.Builder {
+        NetworkId networkId;
+        ConnectPoint ingressPoint;
+        ConnectPoint egressPoint;
+
+        /**
+         * Builder constructor.
+         */
+        private Builder() {
+            // Hide constructor
+        }
+
+        @Override
+        public Builder appId(ApplicationId appId) {
+            return (Builder) super.appId(appId);
+        }
+
+        @Override
+        public Builder key(Key key) {
+            return (Builder) super.key(key);
+        }
+
+        @Override
+        public Builder selector(TrafficSelector selector) {
+            return (Builder) super.selector(selector);
+        }
+
+        @Override
+        public Builder treatment(TrafficTreatment treatment) {
+            return (Builder) super.treatment(treatment);
+        }
+
+        @Override
+        public Builder constraints(List<Constraint> constraints) {
+            return (Builder) super.constraints(constraints);
+        }
+
+        @Override
+        public Builder priority(int priority) {
+            return (Builder) super.priority(priority);
+        }
+
+        @Override
+        public Builder resourceGroup(ResourceGroup resourceGroup) {
+            return (Builder) super.resourceGroup(resourceGroup);
+        }
+
+        /**
+         * Sets the virtual network of the virtual network intent.
+         *
+         * @param networkId virtual network identifier
+         * @return this builder
+         */
+        public VirtualNetworkIntent.Builder networkId(NetworkId networkId) {
+            this.networkId = networkId;
+            return this;
+        }
+
+        /**
+         * Sets the ingress point of the virtual network intent that will be built.
+         *
+         * @param ingressPoint ingress connect point
+         * @return this builder
+         */
+        public VirtualNetworkIntent.Builder ingressPoint(ConnectPoint ingressPoint) {
+            this.ingressPoint = ingressPoint;
+            return this;
+        }
+
+        /**
+         * Sets the egress point of the virtual network intent that will be built.
+         *
+         * @param egressPoint egress connect point
+         * @return this builder
+         */
+        public VirtualNetworkIntent.Builder egressPoint(ConnectPoint egressPoint) {
+            this.egressPoint = egressPoint;
+            return this;
+        }
+
+        /**
+         * Builds a virtual network intent from the accumulated parameters.
+         *
+         * @return virtual network intent
+         */
+        public VirtualNetworkIntent build() {
+
+            return new VirtualNetworkIntent(
+                    networkId,
+                    appId,
+                    key,
+                    selector,
+                    treatment,
+                    ingressPoint,
+                    egressPoint,
+                    constraints,
+                    priority,
+                    resourceGroup
+            );
+        }
+    }
+
+
+    /**
+     * Creates a new point-to-point intent with the supplied ingress/egress
+     * ports and constraints.
+     *
+     * @param networkId    virtual network identifier
+     * @param appId        application identifier
+     * @param key          key of the intent
+     * @param selector     traffic selector
+     * @param treatment    treatment
+     * @param ingressPoint ingress port
+     * @param egressPoint  egress port
+     * @param constraints  optional list of constraints
+     * @param priority     priority to use for flows generated by this intent
+     * @throws NullPointerException if {@code ingressPoint} or
+     *                              {@code egressPoints} or {@code appId} is null.
+     */
+    private VirtualNetworkIntent(NetworkId networkId,
+                                 ApplicationId appId,
+                                 Key key,
+                                 TrafficSelector selector,
+                                 TrafficTreatment treatment,
+                                 ConnectPoint ingressPoint,
+                                 ConnectPoint egressPoint,
+                                 List<Constraint> constraints,
+                                 int priority,
+                                 ResourceGroup resourceGroup) {
+        super(appId, key, Collections.emptyList(), selector, treatment, constraints,
+              priority, resourceGroup);
+
+        checkNotNull(networkId, NETWORK_ID_NULL);
+        checkArgument(!ingressPoint.equals(egressPoint),
+                      "ingress and egress should be different (ingress: %s, egress: %s)", ingressPoint, egressPoint);
+
+        this.networkId = networkId;
+        this.ingressPoint = checkNotNull(ingressPoint);
+        this.egressPoint = checkNotNull(egressPoint);
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected VirtualNetworkIntent() {
+        super();
+        this.networkId = null;
+        this.ingressPoint = null;
+        this.egressPoint = null;
+    }
+
+    /**
+     * Returns the virtual network identifier.
+     *
+     * @return network identifier
+     */
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    /**
+     * Returns the port on which the ingress traffic should be connected to
+     * the egress.
+     *
+     * @return ingress port
+     */
+    public ConnectPoint ingressPoint() {
+        return ingressPoint;
+    }
+
+    /**
+     * Returns the port on which the traffic should egress.
+     *
+     * @return egress port
+     */
+    public ConnectPoint egressPoint() {
+        return egressPoint;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("networkId", networkId)
+                .add("id", id())
+                .add("key", key())
+                .add("appId", appId())
+                .add("priority", priority())
+                .add("resources", resources())
+                .add("selector", selector())
+                .add("treatment", treatment())
+                .add("ingress", ingressPoint)
+                .add("egress", egressPoint)
+                .add("constraints", constraints())
+                .add("resourceGroup", resourceGroup())
+                .toString();
+    }
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkIntentStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkIntentStore.java
new file mode 100644
index 0000000..f8b42c1
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkIntentStore.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.Key;
+
+import java.util.List;
+
+public interface VirtualNetworkIntentStore
+        extends VirtualStore<IntentEvent, IntentStoreDelegate> {
+
+    /**
+     * Returns the number of intents in the store.
+     *
+     * @param networkId the virtual network identifier
+     * @return the number of intents in the store
+     */
+    long getIntentCount(NetworkId networkId);
+
+    /**
+     * Returns an iterable of all intents in the store.
+     *
+     * @param networkId the virtual network identifier
+     * @return iterable  of all intents
+     */
+    Iterable<Intent> getIntents(NetworkId networkId);
+
+    /**
+     * Returns an iterable of all intent data objects in the store.
+     *
+     * @param networkId the virtual network identifier
+     * @param localOnly should only intents for which this instance is master
+     *                  be returned
+     * @param olderThan specified duration in milliseconds (0 for "now")
+     * @return iterable of all intent data objects
+     */
+    Iterable<IntentData> getIntentData(NetworkId networkId, boolean localOnly,
+                                       long olderThan);
+
+    /**
+     * Returns the state of the specified intent.
+     *
+     * @param networkId the virtual network identifier
+     * @param intentKey intent identification
+     * @return current intent state
+     */
+    IntentState getIntentState(NetworkId networkId, Key intentKey);
+
+    /**
+     * Returns the list of the installable events associated with the specified
+     * original intent.
+     *
+     * @param networkId the virtual network identifier
+     * @param intentKey original intent identifier
+     * @return compiled installable intents, or null if no installables exist
+     */
+    List<Intent> getInstallableIntents(NetworkId networkId, Key intentKey);
+
+    /**
+     * Writes an IntentData object to the store.
+     *
+     * @param networkId the virtual network identifier
+     * @param newData new intent data to write
+     */
+    void write(NetworkId networkId, IntentData newData);
+
+    /**
+     * Writes a batch of IntentData objects to the store. A batch has no
+     * semantics, this is simply a convenience API.
+     *
+     * @param networkId the virtual network identifier
+     * @param updates collection of intent data objects to write
+     */
+    void batchWrite(NetworkId networkId, Iterable<IntentData> updates);
+
+    /**
+     * Returns the intent with the specified identifier.
+     *
+     * @param networkId the virtual network identifier
+     * @param key key
+     * @return intent or null if not found
+     */
+    Intent getIntent(NetworkId networkId, Key key);
+
+    /**
+     * Returns the intent data object associated with the specified key.
+     *
+     * @param networkId the virtual network identifier
+     * @param key key to look up
+     * @return intent data object
+     */
+    IntentData getIntentData(NetworkId networkId, Key key);
+
+    /**
+     * Adds a new operation, which should be persisted and delegated.
+     *
+     * @param networkId the virtual network identifier
+     * @param intent operation
+     */
+    void addPending(NetworkId networkId, IntentData intent);
+
+    /**
+     * Checks to see whether the calling instance is the master for processing
+     * this intent, or more specifically, the key contained in this intent.
+     *
+     * @param networkId the virtual network identifier
+     * @param intentKey intentKey to check
+     * @return true if master; false, otherwise
+     */
+    //TODO better name
+    boolean isMaster(NetworkId networkId, Key intentKey);
+
+    /**
+     * Returns the intent requests pending processing.
+     *
+     * @param networkId the virtual network identifier
+     * @return pending intents
+     */
+    Iterable<Intent> getPending(NetworkId networkId);
+
+    /**
+     * Returns the intent data objects that are pending processing.
+     *
+     * @param networkId the virtual network identifier
+     * @return pending intent data objects
+     */
+    Iterable<IntentData> getPendingData(NetworkId networkId);
+
+    /**
+     * Returns the intent data object that are pending processing for a specfied
+     * key.
+     *
+     * @param networkId the virtual network identifier
+     * @param intentKey key to look up
+     * @return pending intent data object
+     */
+    IntentData getPendingData(NetworkId networkId, Key intentKey);
+
+    /**
+     * Returns the intent data objects that are pending processing for longer
+     * than the specified duration.
+     *
+     * @param networkId the virtual network identifier
+     * @param localOnly  should only intents for which this instance is master
+     *                   be returned
+     * @param olderThan specified duration in milliseconds (0 for "now")
+     * @return pending intent data objects
+     */
+    Iterable<IntentData> getPendingData(NetworkId networkId, boolean localOnly, long olderThan);
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkListener.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkListener.java
new file mode 100644
index 0000000..cc6d663
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Represents entity capable of receiving virtual network events.
+ */
+public interface VirtualNetworkListener extends EventListener<VirtualNetworkEvent> {
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkMastershipStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkMastershipStore.java
new file mode 100644
index 0000000..31fdfcd
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkMastershipStore.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Manages inventory of mastership roles for devices, across controller
+ * instances for virtual networks; not intended for direct use.
+ */
+public interface VirtualNetworkMastershipStore
+        extends VirtualStore<MastershipEvent, MastershipStoreDelegate> {
+
+    // three things to map: NodeId, DeviceId, MastershipRole
+
+    /**
+     * Requests role of the local node for the specified device.
+     *
+     * @param networkId the virtual network identifier
+     * @param deviceId  the device identifier
+     * @return established or newly negotiated mastership role
+     */
+    CompletableFuture<MastershipRole> requestRole(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns the role of a device for a specific controller instance.
+     *
+     * @param networkId virtual network identifier
+     * @param nodeId    the instance identifier
+     * @param deviceId  the device identifiers
+     * @return the role
+     */
+    MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId);
+
+    /**
+     * Returns the master for a device.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId  the device identifier
+     * @return the instance identifier of the master
+     */
+    NodeId getMaster(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns the master and backup nodes for a device.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId  the device identifier
+     * @return a RoleInfo containing controller IDs
+     */
+    RoleInfo getNodes(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns the mastership info for a device.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId  the device identifier
+     * @return the mastership info
+     */
+    MastershipInfo getMastership(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns the devices that a controller instance is master of.
+     *
+     * @param networkId virtual network identifier
+     * @param nodeId    the instance identifier
+     * @return a set of device identifiers
+     */
+    Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId);
+
+
+    /**
+     * Sets a device's role for a specified controller instance.
+     *
+     * @param networkId virtual network identifier
+     * @param nodeId    controller instance identifier
+     * @param deviceId  device identifier
+     * @return a mastership event
+     */
+    CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
+                                                 NodeId nodeId, DeviceId deviceId);
+
+    /**
+     * Returns the current master and number of past mastership hand-offs
+     * (terms) for a device.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId  the device identifier
+     * @return the current master's ID and the term value for device, or null
+     */
+    MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Sets a controller instance's mastership role to STANDBY for a device.
+     * If the role is MASTER, another controller instance will be selected
+     * as a candidate master.
+     *
+     * @param networkId virtual network identifier
+     * @param nodeId    the controller instance identifier
+     * @param deviceId  device to revoke mastership role for
+     * @return a mastership event
+     */
+    CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
+                                                  NodeId nodeId, DeviceId deviceId);
+
+    /**
+     * Allows a controller instance to give up its current role for a device.
+     * If the role is MASTER, another controller instance will be selected
+     * as a candidate master.
+     *
+     * @param networkId virtual network identifier
+     * @param nodeId    the controller instance identifier
+     * @param deviceId  device to revoke mastership role for
+     * @return a mastership event
+     */
+    CompletableFuture<MastershipEvent> relinquishRole(NetworkId networkId,
+                                                      NodeId nodeId, DeviceId deviceId);
+
+    /**
+     * Removes all the roles for the specified controller instance.
+     * If the role was MASTER, another controller instance will be selected
+     * as a candidate master.
+     *
+     * @param networkId virtual network identifier
+     * @param nodeId    the controller instance identifier
+     */
+    void relinquishAllRole(NetworkId networkId, NodeId nodeId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkMeterStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkMeterStore.java
new file mode 100644
index 0000000..954f66b
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkMeterStore.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterFeatures;
+import org.onosproject.net.meter.MeterFeaturesKey;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterStoreDelegate;
+import org.onosproject.net.meter.MeterStoreResult;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+public interface VirtualNetworkMeterStore
+        extends VirtualStore<MeterEvent, MeterStoreDelegate> {
+
+    /**
+     * Adds a meter to the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param meter a meter
+     * @return a future indicating the result of the store operation
+     */
+    CompletableFuture<MeterStoreResult> storeMeter(NetworkId networkId, Meter meter);
+
+    /**
+     * Deletes a meter from the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param meter a meter
+     * @return a future indicating the result of the store operation
+     */
+    CompletableFuture<MeterStoreResult> deleteMeter(NetworkId networkId, Meter meter);
+
+
+    /**
+     * Adds the meter features to the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param meterfeatures the meter features
+     * @return the result of the store operation
+     */
+    MeterStoreResult storeMeterFeatures(NetworkId networkId, MeterFeatures meterfeatures);
+
+    /**
+     * Deletes the meter features from the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param deviceId the device id
+     * @return a future indicating the result of the store operation
+     */
+    MeterStoreResult deleteMeterFeatures(NetworkId networkId, DeviceId deviceId);
+
+
+    /**
+     * Updates a meter whose meter id is the same as the passed meter.
+     *
+     * @param networkId a virtual network identifier
+     * @param meter a new meter
+     * @return a future indicating the result of the store operation
+     */
+    CompletableFuture<MeterStoreResult> updateMeter(NetworkId networkId, Meter meter);
+
+    /**
+     * Updates a given meter's state with the provided state.
+     *
+     * @param networkId a virtual network identifier
+     * @param meter a meter
+     */
+    void updateMeterState(NetworkId networkId, Meter meter);
+
+    /**
+     * Obtains a meter matching the given meter key.
+     *
+     * @param networkId a virtual network identifier
+     * @param key a meter key
+     * @return a meter
+     */
+    Meter getMeter(NetworkId networkId, MeterKey key);
+
+    /**
+     * Returns all meters stored in the store.
+     *
+     * @param networkId a virtual network identifier
+     * @return a collection of meters
+     */
+    Collection<Meter> getAllMeters(NetworkId networkId);
+
+    /**
+     * Update the store by deleting the failed meter.
+     * Notifies the delegate that the meter failed to allow it
+     * to nofity the app.
+     *
+     * @param networkId a virtual network identifier
+     * @param op a failed meter operation
+     * @param reason a failure reason
+     */
+    void failedMeter(NetworkId networkId, MeterOperation op, MeterFailReason reason);
+
+    /**
+     * Delete this meter immediately.
+     *
+     * @param networkId a virtual network identifier
+     * @param m a meter
+     */
+    void deleteMeterNow(NetworkId networkId, Meter m);
+
+    /**
+     * Retrieve maximum meters available for the device.
+     *
+     * @param networkId a virtual network identifier
+     * @param key the meter features key
+     * @return the maximum number of meters supported by the device
+     */
+    long getMaxMeters(NetworkId networkId, MeterFeaturesKey key);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkPacketStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkPacketStore.java
new file mode 100644
index 0000000..4cf5cb9
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkPacketStore.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStoreDelegate;
+
+import java.util.List;
+
+public interface VirtualNetworkPacketStore
+        extends VirtualStore<PacketEvent, PacketStoreDelegate> {
+    /**
+     * Decides which instance should emit the packet and forwards the packet to
+     * that instance. The relevant PacketManager is notified via the
+     * PacketStoreDelegate that it should emit the packet.
+     *
+     * @param networkId a virtual network identifier
+     * @param packet the packet to emit
+     */
+    void emit(NetworkId networkId, OutboundPacket packet);
+
+    /**
+     * Requests intercept of packets that match the given selector.
+     *
+     * @param networkId a virtual network identifier
+     * @param request a packet request
+     */
+    void requestPackets(NetworkId networkId, PacketRequest request);
+
+    /**
+     * Cancels intercept of packets that match the given selector.
+     *
+     * @param networkId a virtual network identifier
+     * @param request a packet request
+     */
+    void cancelPackets(NetworkId networkId, PacketRequest request);
+
+    /**
+     * Obtains all existing requests in the system.
+     *
+     * @param networkId a virtual network identifier
+     * @return list of packet requests in order of priority
+     */
+    List<PacketRequest> existingRequests(NetworkId networkId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkService.java
new file mode 100644
index 0000000..d0e4fd1
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkService.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.TenantId;
+
+import java.util.Set;
+
+/**
+ * Service for querying virtual network inventory.
+ */
+@Beta
+public interface VirtualNetworkService
+        extends ListenerService<VirtualNetworkEvent, VirtualNetworkListener> {
+
+    /**
+     * The topic used for obtaining globally unique ids.
+     */
+    String VIRTUAL_NETWORK_TOPIC = "virtual-network-ids";
+
+    /**
+     * Returns a collection of all virtual networks created on behalf of the
+     * specified tenant.
+     *
+     * @param tenantId tenant identifier
+     * @return collection of networks
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    Set<VirtualNetwork> getVirtualNetworks(TenantId tenantId);
+
+    /**
+     * Returns the virtual network matching the network identifier.
+     *
+     * @param networkId virtual network identifier
+     * @return virtual network instance
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    VirtualNetwork getVirtualNetwork(NetworkId networkId);
+
+    /**
+     * Returns {@code tenantId} for specified virtual network id.
+     *
+     * @param networkId virtual network identifier
+     * @return tenantId tenant identifier
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    TenantId getTenantId(NetworkId networkId);
+
+    /**
+     * Returns a collection of all virtual devices in the specified network.
+     *
+     * @param networkId network identifier
+     * @return collection of devices
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    Set<VirtualDevice> getVirtualDevices(NetworkId networkId);
+
+    /**
+     * Returns a collection of all virtual hosts in the specified network.
+     *
+     * @param networkId network identifier
+     * @return collection of hosts
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    Set<VirtualHost> getVirtualHosts(NetworkId networkId);
+
+    /**
+     * Returns collection of all virtual links in the specified network.
+     *
+     * @param networkId network identifier
+     * @return collection of links
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    Set<VirtualLink> getVirtualLinks(NetworkId networkId);
+
+    /**
+     * Returns list of all virtual ports of the specified device. If the
+     * device identifier is null then all of the virtual ports in the specified
+     * network will be returned.
+     *
+     * @param networkId network identifier
+     * @param deviceId  device identifier
+     * @return list of ports
+     * @throws org.onlab.util.ItemNotFoundException if no such network found
+     */
+    Set<VirtualPort> getVirtualPorts(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns list of physical device identifier mapping with the virtual
+     * device in the specified network. The physical devices are specified by
+     * port mapping mechanism.
+     *
+     * @param networkId network identifier
+     * @param deviceId the virtual device identifier
+     * @return collection of the specified device's identifier
+     */
+    Set<DeviceId> getPhysicalDevices(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Returns implementation of the specified service class for operating
+     * in the context of the given network.
+     * <p>
+     * The following services will be available:
+     * <ul>
+     * <li>{@link org.onosproject.net.device.DeviceService}</li>
+     * <li>{@link org.onosproject.net.link.LinkService}</li>
+     * <li>{@link org.onosproject.net.host.HostService}</li>
+     * <li>{@link org.onosproject.net.topology.TopologyService}</li>
+     * <li>{@link org.onosproject.net.topology.PathService}</li>
+     * <li>{@link org.onosproject.net.packet.PacketService}</li>
+     * <li>{@link org.onosproject.net.flow.FlowRuleService}</li>
+     * <li>{@link org.onosproject.net.flowobjective.FlowObjectiveService}</li>
+     * <li>{@link org.onosproject.net.intent.IntentService}</li>
+     * <li>{@link org.onosproject.mastership.MastershipService}</li>
+     * <li>{@link org.onosproject.mastership.MastershipAdminService}</li>
+     * <li>{@link org.onosproject.mastership.MastershipTermService}</li>
+     * </ul>
+     *
+     * @param networkId    network identifier
+     * @param serviceClass service class
+     * @param <T>          type of service
+     * @return implementation class
+     * @throws org.onlab.util.ItemNotFoundException    if no such network found
+     * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+     */
+    <T> T get(NetworkId networkId, Class<T> serviceClass);
+
+    /**
+     * Returns service directory.
+     *
+     * @return a service directory
+     */
+    ServiceDirectory getServiceDirectory();
+
+    /**
+     * Returns the application identifier for a virtual network.
+     *
+     * @param networkId network identifier
+     * @return an representative application identifier for a virtual network
+     */
+    ApplicationId getVirtualNetworkApplicationId(NetworkId networkId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkStore.java
new file mode 100644
index 0000000..de74333
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkStore.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TenantId;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.store.Store;
+
+import java.util.Set;
+
+/**
+ * Mechanism for distributing and storing virtual network model information.
+ */
+public interface VirtualNetworkStore
+        extends Store<VirtualNetworkEvent, VirtualNetworkStoreDelegate> {
+
+    /**
+     * Adds a new tenant ID to the store.
+     *
+     * @param tenantId tenant identifier
+     */
+    void addTenantId(TenantId tenantId);
+
+    /**
+     * Removes the specified tenant ID from the store.
+     *
+     * @param tenantId tenant identifier
+     */
+    void removeTenantId(TenantId tenantId);
+
+    /**
+     * Returns set of registered tenant IDs.
+     *
+     * @return set of tenant identifiers
+     */
+    Set<TenantId> getTenantIds();
+
+    /**
+     * Adds a new virtual network for the specified tenant to the store.
+     *
+     * @param tenantId tenant identifier
+     * @return the virtual network
+     */
+    VirtualNetwork addNetwork(TenantId tenantId);
+
+    /**
+     * Removes the specified virtual network from the store.
+     *
+     * @param networkId network identifier
+     */
+    void removeNetwork(NetworkId networkId);
+
+    /**
+     * Adds a new virtual device to the store. This device will have no ports.
+     *
+     * @param networkId network identifier
+     * @param deviceId  device identifier
+     * @return the virtual device
+     */
+    VirtualDevice addDevice(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Removes the specified virtual device from the given network.
+     *
+     * @param networkId network identifier
+     * @param deviceId  device identifier
+     */
+    void removeDevice(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Adds a new virtual host to the store.
+     *
+     * @param networkId network identifier
+     * @param hostId    host identifier
+     * @param mac       mac address
+     * @param vlan      vlan identifier
+     * @param location  host location
+     * @param ips       set of ip addresses
+     * @return the virtual host
+     */
+    VirtualHost addHost(NetworkId networkId, HostId hostId, MacAddress mac,
+                        VlanId vlan, HostLocation location, Set<IpAddress> ips);
+
+    /**
+     * Removes the specified virtual host from the store.
+     *
+     * @param networkId network identifier
+     * @param hostId    host identifier
+     */
+    void removeHost(NetworkId networkId, HostId hostId);
+
+    /**
+     * Adds a new virtual link.
+     *
+     * @param networkId  network identifier
+     * @param src        source end-point of the link
+     * @param dst        destination end-point of the link
+     * @param state      link state
+     * @param realizedBy underlying tunnel identifier using which this link is realized
+     * @return the virtual link
+     */
+    VirtualLink addLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst, Link.State state, TunnelId realizedBy);
+
+    /**
+     * Updates the tunnelId in the virtual link.
+     *
+     * @param virtualLink virtual link
+     * @param tunnelId    tunnel identifier
+     * @param state       link state
+     */
+    void updateLink(VirtualLink virtualLink, TunnelId tunnelId, Link.State state);
+
+    /**
+     * Removes the specified link from the store.
+     *
+     * @param networkId network identifier
+     * @param src       source connection point
+     * @param dst       destination connection point
+     * @return the virtual link
+     */
+    VirtualLink removeLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst);
+
+    /**
+     * Adds a new virtual port to the network.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   device identifier
+     * @param portNumber port number
+     * @param realizedBy underlying port which realizes the virtual port
+     * @return the virtual port
+     */
+    VirtualPort addPort(NetworkId networkId, DeviceId deviceId,
+                        PortNumber portNumber, ConnectPoint realizedBy);
+
+    /**
+     * Binds an existing virtual port to the network.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   device identifier
+     * @param portNumber port number
+     * @param realizedBy underlying port which realizes the virtual port
+     */
+     void bindPort(NetworkId networkId, DeviceId deviceId,
+                        PortNumber portNumber, ConnectPoint realizedBy);
+
+    /**
+     * Updates port state of an existing virtual port.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   device identifier
+     * @param portNumber port number
+     * @param isEnabled  indicator whether the port is up and active
+     */
+     void updatePortState(NetworkId networkId, DeviceId deviceId,
+                        PortNumber portNumber, boolean isEnabled);
+
+    /**
+     * Removes the specified port from the given device and network.
+     *
+     * @param networkId  network identifier
+     * @param deviceId   device identifier
+     * @param portNumber port number
+     */
+    void removePort(NetworkId networkId, DeviceId deviceId, PortNumber portNumber);
+
+    /**
+     * Returns the list of networks.
+     *
+     * @param tenantId tenant identifier
+     * @return set of virtual networks
+     */
+    Set<VirtualNetwork> getNetworks(TenantId tenantId);
+
+    /**
+     * Returns the virtual network for the given network identifier.
+     *
+     * @param networkId network identifier
+     * @return the virtual network
+     */
+    VirtualNetwork getNetwork(NetworkId networkId);
+
+    /**
+     * Returns the list of devices in the specified virtual network.
+     *
+     * @param networkId network identifier
+     * @return set of virtual devices
+     */
+    Set<VirtualDevice> getDevices(NetworkId networkId);
+
+    /**
+     * Returns the list of hosts in the specified virtual network.
+     *
+     * @param networkId network identifier
+     * @return set of virtual hosts
+     */
+    Set<VirtualHost> getHosts(NetworkId networkId);
+
+    /**
+     * Returns the list of virtual links in the specified virtual network.
+     *
+     * @param networkId network identifier
+     * @return set of virtual links
+     */
+    Set<VirtualLink> getLinks(NetworkId networkId);
+
+    /**
+     * Returns the virtual link matching the network identifier, source connect point,
+     * and destination connect point.
+     *
+     * @param networkId network identifier
+     * @param src       source connect point
+     * @param dst       destination connect point
+     * @return virtual link
+     */
+    VirtualLink getLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst);
+
+    /**
+     * Returns the list of ports of the specified virtual device.
+     *
+     * @param networkId network identifier
+     * @param deviceId  device identifier
+     * @return set of virtual networks
+     */
+    Set<VirtualPort> getPorts(NetworkId networkId, DeviceId deviceId);
+
+    /**
+     * Adds the intent to tunnel identifier mapping to the store.
+     *
+     * @param intent   intent
+     * @param tunnelId tunnel identifier
+     * @deprecated in Kingfisher Release (1.10)
+     */
+    @Deprecated
+    void addTunnelId(Intent intent, TunnelId tunnelId);
+
+    /**
+     * Return the set of tunnel identifiers store against the intent.
+     *
+     * @param intent intent
+     * @return set of tunnel identifiers
+     * @deprecated in Kingfisher Release (1.10)
+     */
+    @Deprecated
+    Set<TunnelId> getTunnelIds(Intent intent);
+
+    /**
+     * Removes the intent to tunnel identifier mapping from the store.
+     *
+     * @param intent   intent
+     * @param tunnelId tunnel identifier
+     * @deprecated in Kingfisher Release (1.10)
+     */
+    @Deprecated
+    void removeTunnelId(Intent intent, TunnelId tunnelId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkStoreDelegate.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkStoreDelegate.java
new file mode 100644
index 0000000..202a0be
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualNetworkStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Network configuration store delegate abstraction.
+ */
+public interface VirtualNetworkStoreDelegate extends StoreDelegate<VirtualNetworkEvent> {
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPacketContext.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPacketContext.java
new file mode 100644
index 0000000..42b548a
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPacketContext.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.packet.PacketContext;
+
+/**
+ * Represents context for processing an inbound packet for a virtual network,
+ * and (optionally) emitting a corresponding outbound packet.
+ */
+public interface VirtualPacketContext extends PacketContext {
+    /**
+     * Returns the network identifier.
+     *
+     * @return network id
+     */
+    NetworkId networkId();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java
new file mode 100644
index 0000000..a393e8e
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPort.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Port;
+
+/**
+ * Representation of a virtual port.
+ */
+@Beta
+public interface VirtualPort extends VirtualElement, Port {
+
+    /**
+     * Returns the underlying port using which this port is realized.
+     *
+     * @return underlying port which realizes this virtual port
+     */
+    ConnectPoint realizedBy();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPortDescription.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPortDescription.java
new file mode 100644
index 0000000..0785641
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualPortDescription.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.net.device.PortDescription;
+
+/**
+ * Information about a virtual port.
+ */
+public interface VirtualPortDescription extends PortDescription {
+
+    // TODO: Add something about a requirement of virtual port.
+
+    /**
+     * Representation of the virtual port.
+     */
+    enum State {
+        /**
+         * Signifies that a virtual port is currently start.
+         */
+        START,
+
+        /**
+         * Signifies that a virtual port is currently stop.
+         */
+        STOP,
+
+        /**
+         * Signifies that a virtual port is pause for a moment.
+         */
+        PAUSE
+    }
+
+    /**
+     * Starts the virtual port.
+     */
+    void start();
+
+    /**
+     * Stops the virtual port.
+     */
+    void stop();
+
+    /**
+     * Pauses the virtual port for stopping a network or device.
+     * e.g. snapshot.
+     */
+    void pause();
+
+    /**
+     * Returns the virtual port state.
+     *
+     * @return state of virtual port
+     */
+    State state();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualStore.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualStore.java
new file mode 100644
index 0000000..8a25874
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VirtualStore.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onosproject.event.Event;
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Abstraction of a entity capable of storing and/or distributing information
+ * for virtual network across a cluster.
+ */
+public interface VirtualStore<E extends Event, D extends StoreDelegate<E>> {
+    /**
+     * Sets the delegate on the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param delegate new store delegate
+     * @throws java.lang.IllegalStateException if a delegate is already
+     *                                         currently set on the store and is a different one that
+     */
+    void setDelegate(NetworkId networkId, D delegate);
+
+    /**
+     * Withdraws the delegate from the store.
+     *
+     * @param networkId a virtual network identifier
+     * @param delegate store delegate to withdraw
+     * @throws java.lang.IllegalArgumentException if the delegate is not
+     *                                            currently set on the store
+     */
+    void unsetDelegate(NetworkId networkId, D delegate);
+
+    /**
+     * Indicates whether the store has a delegate.
+     *
+     * @param networkId a virtual network identifier
+     * @return true if delegate is set
+     */
+    boolean hasDelegate(NetworkId networkId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VnetService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VnetService.java
new file mode 100644
index 0000000..1889ad4
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/VnetService.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+/**
+ * Virtual network service interface.
+ */
+public interface VnetService {
+    NetworkId networkId();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualDeviceCodec.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualDeviceCodec.java
new file mode 100644
index 0000000..c0c8cf8
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualDeviceCodec.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.codec;
+
+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(NETWORK_ID, vDev.networkId().toString())
+                .put(ID, vDev.id().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);
+    }
+
+    /**
+     * 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/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualHostCodec.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualHostCodec.java
new file mode 100644
index 0000000..2ddb589
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualHostCodec.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.virtual.DefaultVirtualHost;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * Codec for the VirtualHost class.
+ */
+public class VirtualHostCodec extends JsonCodec<VirtualHost> {
+
+    // JSON field names
+    static final String NETWORK_ID = "networkId";
+    static final String HOST_ID = "id";
+    static final String MAC_ADDRESS = "mac";
+    static final String VLAN = "vlan";
+    static final String IP_ADDRESSES = "ipAddresses";
+    static final String HOST_LOCATION = "locations";
+
+    private static final String NULL_OBJECT_MSG = "VirtualHost cannot be null";
+    private static final String MISSING_MEMBER_MSG = " member is required in VirtualHost";
+
+    @Override
+    public ObjectNode encode(VirtualHost vHost, CodecContext context) {
+        checkNotNull(vHost, NULL_OBJECT_MSG);
+
+        final JsonCodec<HostLocation> locationCodec =
+                context.codec(HostLocation.class);
+        final ObjectNode result = context.mapper().createObjectNode()
+                .put(NETWORK_ID, vHost.networkId().toString())
+                .put(HOST_ID, vHost.id().toString())
+                .put(MAC_ADDRESS, vHost.mac().toString())
+                .put(VLAN, vHost.vlan().toString());
+
+        final ArrayNode jsonIpAddresses = result.putArray(IP_ADDRESSES);
+        for (final IpAddress ipAddress : vHost.ipAddresses()) {
+            jsonIpAddresses.add(ipAddress.toString());
+        }
+        result.set(IP_ADDRESSES, jsonIpAddresses);
+
+        final ArrayNode jsonLocations = result.putArray("locations");
+        for (final HostLocation location : vHost.locations()) {
+            jsonLocations.add(locationCodec.encode(location, context));
+        }
+        result.set("locations", jsonLocations);
+
+        return result;
+    }
+
+    @Override
+    public VirtualHost decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        NetworkId nId = NetworkId.networkId(Long.parseLong(extractMember(NETWORK_ID, json)));
+        MacAddress mac = MacAddress.valueOf(json.get("mac").asText());
+        VlanId vlanId = VlanId.vlanId((short) json.get("vlan").asInt(VlanId.UNTAGGED));
+
+        Set<HostLocation> locations = new HashSet<>();
+        JsonNode locationNodes = json.get("locations");
+        locationNodes.forEach(locationNode -> {
+            PortNumber portNumber = PortNumber.portNumber(locationNode.get("port").asText());
+            DeviceId deviceId = DeviceId.deviceId(locationNode.get("elementId").asText());
+            locations.add(new HostLocation(deviceId, portNumber, 0));
+        });
+
+        HostId id = HostId.hostId(mac, vlanId);
+
+        Iterator<JsonNode> ipStrings = json.get("ipAddresses").elements();
+        Set<IpAddress> ips = new HashSet<>();
+        while (ipStrings.hasNext()) {
+            ips.add(IpAddress.valueOf(ipStrings.next().asText()));
+        }
+
+        return new DefaultVirtualHost(nId, id, mac, vlanId, locations, ips);
+    }
+
+    /**
+     * 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/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualLinkCodec.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualLinkCodec.java
new file mode 100644
index 0000000..641982f
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualLinkCodec.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.codec;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+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 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);
+
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(NETWORK_ID, vLink.networkId().toString());
+        JsonCodec<Link> codec = context.codec(Link.class);
+        ObjectNode linkResult = codec.encode(vLink, context);
+        result.setAll(linkResult);
+        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)));
+        return DefaultVirtualLink.builder()
+                .networkId(nId)
+                .src(link.src())
+                .dst(link.dst())
+                .build();
+    }
+
+    /**
+     * 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/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualNetworkCodec.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualNetworkCodec.java
new file mode 100644
index 0000000..32945e4
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualNetworkCodec.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.codec;
+
+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.net.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/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualPortCodec.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualPortCodec.java
new file mode 100644
index 0000000..474196e
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/VirtualPortCodec.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.codec;
+
+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.ConnectPoint;
+import org.onosproject.net.DeviceId;
+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().deviceId().toString())
+                .put(PHYS_PORT_NUM, vPort.realizedBy().port().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));
+
+        ConnectPoint realizedBy = new ConnectPoint(physDId, physPortNum);
+        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/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/package-info.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/package-info.java
new file mode 100644
index 0000000..8999592
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/codec/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Network virtualization data model codecs.
+ */
+package org.onosproject.incubator.net.virtual.codec;
\ No newline at end of file
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/AbstractVirtualListenerManager.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/AbstractVirtualListenerManager.java
new file mode 100644
index 0000000..57dfe0c
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/AbstractVirtualListenerManager.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.event;
+
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventListener;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.event.ListenerService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VnetService;
+
+/**
+ * Basis for virtual event components which need to export listener mechanism.
+ */
+public abstract class AbstractVirtualListenerManager
+        <E extends Event, L extends EventListener<E>>
+        implements ListenerService<E, L>, VnetService {
+
+    protected final NetworkId networkId;
+    protected final VirtualNetworkService manager;
+    protected final ServiceDirectory serviceDirectory;
+
+    protected EventDeliveryService eventDispatcher;
+
+    private ListenerRegistry<E, L> listenerRegistry;
+
+    private VirtualListenerRegistryManager listenerManager =
+            VirtualListenerRegistryManager.getInstance();
+
+    public AbstractVirtualListenerManager(VirtualNetworkService manager,
+                                          NetworkId networkId,
+                                          Class<? extends Event> eventClass) {
+        this.manager = manager;
+        this.networkId = networkId;
+        this.serviceDirectory = manager.getServiceDirectory();
+
+        //Set default event delivery service by default
+        this.eventDispatcher = serviceDirectory.get(EventDeliveryService.class);
+
+        //Initialize and reference to the listener registry
+        this.listenerRegistry = listenerManager.getRegistry(networkId, eventClass);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void addListener(L listener) {
+        listenerRegistry.addListener(listener);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void removeListener(L listener) {
+        listenerRegistry.removeListener(listener);
+    }
+
+    /**
+     * Safely posts the specified event to the local event dispatcher.
+     * If there is no event dispatcher or if the event is null, this method
+     * is a noop.
+     *
+     * @param event event to be posted; may be null
+     */
+    protected void post(E event) {
+        if (event != null && eventDispatcher != null) {
+            VirtualEvent<E> vEvent =
+                    new VirtualEvent<E>(networkId, VirtualEvent.Type.POSTED, event);
+            eventDispatcher.post(vEvent);
+        }
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return this.networkId;
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/VirtualEvent.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/VirtualEvent.java
new file mode 100644
index 0000000..bbba983
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/VirtualEvent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.event;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.event.Event;
+import org.onosproject.incubator.net.virtual.NetworkId;
+
+/**
+ * Base class for virtual network event that encapsulates a normal event.
+ */
+public class VirtualEvent<E extends Event>
+        extends AbstractEvent<VirtualEvent.Type, E> {
+
+    private NetworkId networkId;
+
+    /**
+     * Type of virtual network events.
+     */
+    public enum Type {
+        /**
+         * A new virtual event has been posted.
+         */
+        POSTED
+    }
+
+    protected VirtualEvent(NetworkId networkId, Type type, E subject) {
+        super(type, subject);
+        this.networkId = networkId;
+    }
+
+    protected VirtualEvent(NetworkId networkId, Type type, E subject, long time) {
+        super(type, subject, time);
+        this.networkId = networkId;
+    }
+
+    public NetworkId networkId() {
+        return networkId;
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/VirtualListenerRegistryManager.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/VirtualListenerRegistryManager.java
new file mode 100644
index 0000000..55210dd
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/VirtualListenerRegistryManager.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.event;
+
+import com.google.common.collect.Maps;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventSink;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.incubator.net.virtual.NetworkId;
+
+import java.util.Map;
+
+/**
+ * Base implementation of an virtual event sink and a registry capable of tracking
+ * listeners and dispatching events to them as part of event sink processing.
+ */
+public final class VirtualListenerRegistryManager
+        implements EventSink<VirtualEvent> {
+
+    private Map<NetworkId, Map<Class<? extends Event>, ListenerRegistry>>
+            listenerMapByNetwork = Maps.newConcurrentMap();
+
+    ListenerRegistry lastStart;
+
+    // non-instantiable (except for our Singleton)
+    private VirtualListenerRegistryManager() {
+
+    }
+
+    public static VirtualListenerRegistryManager getInstance() {
+        return SingletonHelper.INSTANCE;
+    }
+
+    public ListenerRegistry getRegistry(NetworkId networkId,
+                                        Class<? extends Event> eventClass) {
+        Map<Class<? extends Event>, ListenerRegistry> listenerMapByEvent =
+                listenerMapByNetwork.get(networkId);
+
+        if (listenerMapByEvent == null) {
+            listenerMapByEvent = Maps.newConcurrentMap();
+            listenerMapByNetwork.putIfAbsent(networkId, listenerMapByEvent);
+        }
+
+        ListenerRegistry listenerRegistry = listenerMapByEvent.get(eventClass);
+
+        if (listenerRegistry == null) {
+            listenerRegistry = new ListenerRegistry();
+            listenerMapByEvent.putIfAbsent(eventClass, listenerRegistry);
+        }
+
+        return listenerRegistry;
+    }
+
+    @Override
+    public void process(VirtualEvent event) {
+        NetworkId networkId = event.networkId();
+        Event originalEvent = (Event) event.subject();
+
+        ListenerRegistry listenerRegistry =
+                listenerMapByNetwork.get(networkId).get(originalEvent.getClass());
+        if (listenerRegistry != null) {
+            listenerRegistry.process(originalEvent);
+            lastStart = listenerRegistry;
+        }
+    }
+
+    @Override
+    public void onProcessLimit() {
+        lastStart.onProcessLimit();
+    }
+
+    /**
+     * Prevents object instantiation from external.
+     */
+    private static final class SingletonHelper {
+        private static final String ILLEGAL_ACCESS_MSG =
+                "Should not instantiate this class.";
+        private static final VirtualListenerRegistryManager INSTANCE =
+                new VirtualListenerRegistryManager();
+
+        private SingletonHelper() {
+            throw new IllegalAccessError(ILLEGAL_ACCESS_MSG);
+        }
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/package-info.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/package-info.java
new file mode 100644
index 0000000..448df90
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/event/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Virtual network event delivery subsystem interfaces &amp; supporting abstractions.
+ */
+package org.onosproject.incubator.net.virtual.event;
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/VirtualIntentCompiler.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/VirtualIntentCompiler.java
new file mode 100644
index 0000000..e76bf6e
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/VirtualIntentCompiler.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.intent;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.intent.Intent;
+
+import java.util.List;
+
+/**
+ * Abstraction of a compiler which is capable of taking an intent
+ * and translating it to other, potentially installable, intents for virtual networks.
+ *
+ * @param <T> the type of intent
+ */
+@Beta
+public interface VirtualIntentCompiler<T extends Intent> {
+    /**
+     * Compiles the specified intent into other intents.
+     *
+     * @param networkId   network identifier
+     * @param intent      intent to be compiled
+     * @param installable previous compilation result; optional
+     * @return list of resulting intents
+     * @throws VirtualIntentException if issues are encountered while compiling the intent
+     */
+     List<Intent> compile(NetworkId networkId, T intent, List<Intent> installable);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/VirtualIntentException.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/VirtualIntentException.java
new file mode 100644
index 0000000..49cb908
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/VirtualIntentException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.intent;
+
+public class VirtualIntentException extends RuntimeException {
+
+    //FIXME: how to obatin UID?
+    private static final long serialVersionUID = 1907263634145241319L;
+
+    /**
+     * Constructs an exception with no message and no underlying cause.
+     */
+    public VirtualIntentException() {
+    }
+
+    /**
+     * Constructs an exception with the specified message.
+     *
+     * @param message the message describing the specific nature of the error
+     */
+    public VirtualIntentException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs an exception with the specified message and the underlying cause.
+     *
+     * @param message the message describing the specific nature of the error
+     * @param cause   the underlying cause of this error
+     */
+    public VirtualIntentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/package-info.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/package-info.java
new file mode 100644
index 0000000..c5673ac
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/intent/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Set of abstractions for conveying high-level intents for treatment of
+ * selected network traffic by allowing applications to express the
+ * <em>what</em> rather than the <em>how</em> for virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.intent;
\ No newline at end of file
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/package-info.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/package-info.java
new file mode 100644
index 0000000..d9ced32
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Network virtualization data models and services.
+ */
+package org.onosproject.incubator.net.virtual;
\ No newline at end of file
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/AbstractVirtualProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/AbstractVirtualProvider.java
new file mode 100644
index 0000000..bb4ba30
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/AbstractVirtualProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.provider.ProviderId;
+
+public abstract class AbstractVirtualProvider implements VirtualProvider {
+    private final ProviderId providerId;
+
+    /**
+     * Creates a virtual provider with the supplied identifier.
+     *
+     * @param id a virtual provider id
+     */
+    protected AbstractVirtualProvider(ProviderId id) {
+        this.providerId = id;
+    }
+
+    @Override
+    public ProviderId id() {
+        return providerId;
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/AbstractVirtualProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/AbstractVirtualProviderService.java
new file mode 100644
index 0000000..2411e26
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/AbstractVirtualProviderService.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of a virtual provider service,
+ * which tracks the provider to which it is issued and can be invalidated.
+ *
+ * @param <P> type of the information provider
+ */
+public abstract class AbstractVirtualProviderService<P extends VirtualProvider>
+        implements VirtualProviderService<P> {
+
+    private boolean isValid = true;
+    private P provider = null;
+
+    /**
+     * Creates a virtual provider service on behalf of the specified provider.
+     *
+     * @param provider provider to which this service is being issued
+     */
+    protected void setProvider(P provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * Invalidates this provider service.
+     */
+    public void invalidate() {
+        isValid = false;
+    }
+
+    /**
+     * Checks the validity of this provider service.
+     *
+     * @throws java.lang.IllegalStateException if the service is no longer valid
+     */
+    public void checkValidity() {
+        checkState(isValid, "Provider service is no longer valid");
+    }
+
+    @Override
+    public P provider() {
+        return provider;
+    }
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/InternalRoutingAlgorithm.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/InternalRoutingAlgorithm.java
new file mode 100644
index 0000000..c17f0d9
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/InternalRoutingAlgorithm.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Path;
+
+/**
+ * Abstraction of an embedding algorithm used for embedding virtual objects
+ * into the underlying physical network.
+ */
+public interface InternalRoutingAlgorithm {
+    /**
+     * Find a route between two connect points (i.e. ports)
+     * according to the own logic.
+     *
+     * @param src A start point
+     * @param dst A sink point
+     * @return The path between src and dst calculated from the algorithm
+     */
+    Path findPath(ConnectPoint src, ConnectPoint dst);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualDeviceProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualDeviceProvider.java
new file mode 100644
index 0000000..9191eaf
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualDeviceProvider.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Abstraction of a virtual device information provider.
+ */
+public interface VirtualDeviceProvider extends VirtualProvider {
+
+    /**
+     * Notifies the provider of a mastership role change for the specified
+     * device as decided by the core.
+     *
+     * @param deviceId  device identifier
+     * @param newRole newly determined mastership role
+     */
+    void roleChanged(DeviceId deviceId, MastershipRole newRole);
+
+    /**
+     * Indicates whether or not the specified connect points on the underlying
+     * network are traversable.
+     *
+     * @param src source connection point
+     * @param dst destination connection point
+     * @return true if the destination is reachable from the source
+     */
+    boolean isTraversable(ConnectPoint src, ConnectPoint dst);
+
+    /**
+     * Indicates whether or not the all physical devices mapped by the given
+     * virtual device are reachable.
+     *
+     * @param deviceId  device identifier
+     * @return true if the all physical devices are reachable, false otherwise
+     */
+    boolean isReachable(DeviceId deviceId);
+
+    /**
+     * Administratively enables or disables a port.
+     *
+     * @param deviceId device identifier
+     * @param portNumber port number
+     * @param enable true if port is to be enabled, false to disable
+     */
+    void changePortState(DeviceId deviceId, PortNumber portNumber,
+                         boolean enable);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualDeviceProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualDeviceProviderService.java
new file mode 100644
index 0000000..e2d584c
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualDeviceProviderService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.virtual.VirtualPortDescription;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.device.PortStatistics;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Service through which virtual device providers can inject virtual device
+ * information into the core.
+ */
+public interface VirtualDeviceProviderService
+        extends VirtualProviderService<VirtualDeviceProvider> {
+
+    /**
+     * Updates information about all ports of a device. It is up to the core to
+     * determine what has changed.
+     *
+     * @param deviceId         identity of the device
+     * @param portDescs list of virtual device ports
+     */
+    void updatePorts(DeviceId deviceId, List<VirtualPortDescription> portDescs);
+
+    /**
+     * Notifies the core about port status change of a single port.
+     *
+     * @param deviceId        identity of the device
+     * @param portDesc description of the virtual port that changed
+     */
+    void portStatusChanged(DeviceId deviceId, VirtualPortDescription portDesc);
+
+    /**
+     * Notifies the core about the result of a RoleRequest sent to a device.
+     *
+     * @param deviceId identity of the device
+     * @param requested mastership role that was requested by the node
+     * @param response mastership role the switch accepted
+     */
+    void receivedRoleReply(DeviceId deviceId, MastershipRole requested,
+                           MastershipRole response);
+
+    /**
+     * Updates statistics about all ports of a device.
+     *
+     * @param deviceId          identity of the device
+     * @param portStatistics  list of device port statistics
+     */
+    void updatePortStatistics(DeviceId deviceId,
+                              Collection<PortStatistics> portStatistics);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java
new file mode 100644
index 0000000..8a4ec34
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+
+/**
+ * Abstraction of a virtual flow rule provider.
+ * This provider virtualizes and de-virtualizes FlowRule.
+ * See {@link org.onosproject.net.flow.FlowRule}.
+ */
+public interface VirtualFlowRuleProvider extends VirtualProvider {
+
+    /**
+     * Instructs the provider to apply the specified flow rules to their
+     * respective virtual devices.
+     *
+     * @param networkId the identity of the virtual network where this rule applies
+     * @param flowRules one or more flow rules
+     */
+    void applyFlowRule(NetworkId networkId, FlowRule... flowRules);
+
+    /**
+     * Instructs the provider to remove the specified flow rules to their
+     * respective virtual devices.
+     *
+     * @param networkId the identity of the virtual network where this rule applies
+     * @param flowRules one or more flow rules
+     */
+    void removeFlowRule(NetworkId networkId, FlowRule... flowRules);
+
+    /**
+     * Installs a batch of flow rules. Each flowrule is associated to an
+     * operation which results in either addition, removal or modification.
+     *
+     * @param networkId the identity of the virtual network where this rule applies
+     * @param batch a batch of flow rules
+     */
+    void executeBatch(NetworkId networkId, FlowRuleBatchOperation batch);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProviderService.java
new file mode 100644
index 0000000..065af9c
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualFlowRuleProviderService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.TableStatisticsEntry;
+
+import java.util.List;
+
+/**
+ * Service through which virtual flow rule providers can inject information into
+ * the core.
+ */
+public interface VirtualFlowRuleProviderService
+        extends VirtualProviderService<VirtualFlowRuleProvider> {
+
+    /**
+     * Signals that a flow rule that was previously installed has been removed.
+     *
+     * @param flowEntry removed flow entry
+     */
+    void flowRemoved(FlowEntry flowEntry);
+
+    /**
+     * Pushes the collection of flow entries currently applied on the given
+     * virtual device.
+     *
+     * @param deviceId device identifier
+     * @param flowEntries collection of flow rules
+     */
+    void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries);
+
+    /**
+     * Pushes the collection of flow entries currently applied on the given
+     * device without flowMissing process.
+     *
+     * @param deviceId device identifier
+     * @param flowEntries collection of flow rules
+     */
+    void pushFlowMetricsWithoutFlowMissing(DeviceId deviceId, Iterable<FlowEntry> flowEntries);
+
+    /**
+     * Pushes the collection of table statistics entries currently extracted
+     * from the given virtual device.
+     *
+     * @param deviceId device identifier
+     * @param tableStatsEntries collection of flow table statistics entries
+     */
+    void pushTableStatistics(DeviceId deviceId, List<TableStatisticsEntry> tableStatsEntries);
+
+    /**
+     * Indicates to the core that the requested batch operation has
+     * been completed.
+     *
+     * @param batchId the batch which was processed
+     * @param operation the resulting outcome of the operation
+     */
+    void batchOperationCompleted(long batchId, CompletedBatchOperation operation);
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualGroupProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualGroupProvider.java
new file mode 100644
index 0000000..72d68c3
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualGroupProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.GroupOperations;
+
+/**
+ * Abstraction of group provider for virtual network.
+ */
+public interface VirtualGroupProvider extends VirtualProvider {
+    /**
+     * Performs a batch of group operation in the specified virtual device
+     * with the specified parameters.
+     *
+     * @param networkId the identity of the virtual network where this rule applies
+     * @param deviceId device identifier on which the batch of group
+     * operations to be executed
+     * @param groupOps immutable list of group operation
+     */
+    void performGroupOperation(NetworkId networkId, DeviceId deviceId,
+                               GroupOperations groupOps);
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualGroupProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualGroupProviderService.java
new file mode 100644
index 0000000..0fdaca8
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualGroupProviderService.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupOperation;
+
+import java.util.Collection;
+
+/**
+ * Service through which Group providers can inject information into
+ * the virtual network core.
+ */
+public interface VirtualGroupProviderService
+        extends VirtualProviderService<VirtualGroupProvider> {
+    /**
+     * Notifies core if any failure from data plane during group operations.
+     *
+     * @param deviceId the device ID
+     * @param operation offended group operation
+     */
+    void groupOperationFailed(DeviceId deviceId, GroupOperation operation);
+
+    /**
+     * Pushes the collection of group detected in the data plane along
+     * with statistics.
+     *
+     * @param deviceId device identifier
+     * @param groupEntries collection of group entries as seen in data plane
+     */
+    void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries);
+
+    /**
+     * Notifies store of group failovers.
+     *
+     * @param failoverGroups failover groups in which a failover has occurred
+     */
+    void notifyOfFailovers(Collection<Group> failoverGroups);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualMeterProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualMeterProvider.java
new file mode 100644
index 0000000..6ac80e4
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualMeterProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterOperations;
+
+/**
+ * Abstraction of a Meter provider for virtual network.
+ */
+public interface VirtualMeterProvider extends VirtualProvider {
+    /**
+     * Performs a batch of meter operation on the specified virtual device
+     * with the specified parameters.
+     *
+     * @param networkId the identity of the virtual network where this rule applies
+     * @param deviceId device identifier on which the batch of group
+     * operations to be executed
+     * @param meterOps immutable list of meter operation
+     */
+    void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+                               MeterOperations meterOps);
+
+
+    /**
+     * Performs a meter operation on the specified vitual device with the
+     * specified parameters.
+     *
+     * @param networkId the identity of the virtual network where this rule applies
+     * @param deviceId device identifier on which the batch of group
+     * operations to be executed
+     * @param meterOp a meter operation
+     */
+    void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+                               MeterOperation meterOp);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualMeterProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualMeterProviderService.java
new file mode 100644
index 0000000..46e4a7d
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualMeterProviderService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterFeatures;
+import org.onosproject.net.meter.MeterOperation;
+
+import java.util.Collection;
+
+/**
+ * Service through which meter providers can inject information
+ * into the virtual network subsystem core.
+ */
+public interface VirtualMeterProviderService
+        extends VirtualProviderService<VirtualMeterProvider> {
+    /**
+     * Notifies the core that a meter operation failed for a
+     * specific reason.
+     *
+     * @param operation the failed operation
+     * @param reason the failure reason
+     */
+    void meterOperationFailed(MeterOperation operation, MeterFailReason reason);
+
+    /**
+     * Pushes the collection of meters observed on the data plane as
+     * well as their associated statistics.
+     *
+     * @param deviceId a device id
+     * @param meterEntries a collection of meter entries
+     */
+    void pushMeterMetrics(DeviceId deviceId, Collection<Meter> meterEntries);
+
+    /**
+     * Pushes the meter features collected from the device.
+     *
+     * @param deviceId the device Id
+     * @param meterfeatures the meter features
+     */
+    void pushMeterFeatures(DeviceId deviceId,
+                           MeterFeatures meterfeatures);
+
+    /**
+     * Delete meter features collected from the device.
+     *
+     * @param deviceId the device id
+     */
+    void deleteMeterFeatures(DeviceId deviceId);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProvider.java
new file mode 100644
index 0000000..038d4b3
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.provider.Provider;
+
+/**
+ * Entity capable of providing traffic isolation constructs for use in
+ * implementation of virtual devices and virtual links.
+ */
+public interface VirtualNetworkProvider extends Provider {
+
+    /**
+     * Indicates whether or not the specified connect points on the underlying
+     * network are traversable/reachable.
+     *
+     * @param src source connection point
+     * @param dst destination connection point
+     * @return true if the destination is reachable from the source
+     */
+    boolean isTraversable(ConnectPoint src, ConnectPoint dst);
+
+    // TODO: Further enhance this interface to support the virtual intent programming across this boundary.
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProviderRegistry.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProviderRegistry.java
new file mode 100644
index 0000000..6fbb1a7
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProviderRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.provider.ProviderRegistry;
+
+/**
+ * Abstraction of a virtual network provider registry.
+ */
+public interface VirtualNetworkProviderRegistry
+        extends ProviderRegistry<VirtualNetworkProvider, VirtualNetworkProviderService> {
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProviderService.java
new file mode 100644
index 0000000..ee7d9a6
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualNetworkProviderService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.provider.ProviderService;
+
+import java.util.Set;
+
+/**
+ * Service through which virtual network providers can inject information into
+ * the core.
+ */
+public interface VirtualNetworkProviderService extends ProviderService<VirtualNetworkProvider> {
+
+    /**
+     * Set of separate topology clusters expressed in terms of connect points which
+     * belong to the same SCC of the underlying topology.
+     *
+     * @param clusters set of sets of mutually reachable connection points;
+     *                 the outer sets are not mutually reachable
+     */
+    void topologyChanged(Set<Set<ConnectPoint>> clusters);
+
+    // TBD: Is the above sufficient to determine health/viability of virtual entities based on
+    // clustering (SCC) of the physical ones?
+
+    /**
+     * This method is used to notify the VirtualNetwork service that a tunnel is now ACTIVE.
+     *
+     * @param networkId network identifier
+     * @param src       source connection point
+     * @param dst       destination connection point
+     * @param tunnelId  tunnel identifier
+     */
+    void tunnelUp(NetworkId networkId, ConnectPoint src, ConnectPoint dst, TunnelId tunnelId);
+
+    /**
+     * This method is used to notify the VirtualNetwork service that a tunnel is now
+     * FAILED or INACTIVE.
+     *
+     * @param networkId network identifier
+     * @param src       source connection point
+     * @param dst       destination connection point
+     * @param tunnelId  tunnel identifier
+     */
+    void tunnelDown(NetworkId networkId, ConnectPoint src, ConnectPoint dst, TunnelId tunnelId);
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualPacketProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualPacketProvider.java
new file mode 100644
index 0000000..4cc8d80
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualPacketProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.packet.OutboundPacket;
+
+/**
+ * Abstraction of a virtual packet provider capable of emitting packets
+ * from virtual network core services to the underlay network.
+ * This provider de-virtualizes and virtualize PacketContext.
+ */
+public interface VirtualPacketProvider extends VirtualProvider {
+    /**
+     * Emits the specified outbound packet onto the underlay physical network.
+     * This provider maps the requested packets for physical network.
+     *
+     * @param networkId the virtual network ID
+     * @param packet outbound packet in the context of virtual network
+     */
+    void emit(NetworkId networkId, OutboundPacket packet);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualPacketProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualPacketProviderService.java
new file mode 100644
index 0000000..3c2b4c2
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualPacketProviderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.packet.PacketContext;
+
+/**
+ * Entity capable of processing inbound packets for virtual networks.
+ */
+public interface VirtualPacketProviderService
+        extends VirtualProviderService<VirtualPacketProvider> {
+
+    /**
+     * Submits inbound packet context for processing to virtual network core.
+     * This processing will be done synchronously, i.e. run-to-completion.
+     * This context is translated with virtual concepts.
+     *
+     * @param context inbound packet context
+     */
+    void processPacket(PacketContext context);
+
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProvider.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProvider.java
new file mode 100644
index 0000000..ac0dfb6
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Abstraction of a provider of information about virtual network environment.
+ * The role of virtual providers is to translate virtual objects into physical
+ * objects, vice versa.
+ */
+public interface VirtualProvider {
+
+    /**
+     * Returns the provider identifier.
+     *
+     * @return provider identification
+     */
+    ProviderId id();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProviderRegistryService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProviderRegistryService.java
new file mode 100644
index 0000000..546463c
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProviderRegistryService.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Set;
+
+/**
+ * Registry for tracking information providers with the core.
+ */
+public interface VirtualProviderRegistryService {
+
+    /**
+     * Registers the supplied virtual provider.
+     *
+     * @param virtualProvider a virtual provider to be registered
+     * @throws java.lang.IllegalArgumentException if the provider is registered already
+     */
+    void registerProvider(VirtualProvider virtualProvider);
+
+    /**
+     * Unregisters the supplied virtual provider.
+     * As a result the previously issued virtual provider service will be invalidated
+     * and any subsequent invocations of its methods may throw
+     * {@link java.lang.IllegalStateException}.
+     * <p>
+     * Unregistering a virtual provider that has not been previously registered results
+     * in a no-op.
+     * </p>
+     *
+     * @param virtualProvider a virtual provider to be unregistered
+     */
+    void unregisterProvider(VirtualProvider virtualProvider);
+
+    /**
+     * Registers the supplied virtual provider.
+     *
+     * @param networkId a virtual network identifier
+     * @param virtualProviderService a virtual provider service to be registered
+     */
+    void registerProviderService(NetworkId networkId,
+                                 VirtualProviderService virtualProviderService);
+
+    /**
+     * Unregisters the supplied virtual provider service.
+     *
+     * @param networkId a virtual network identifier
+     * @param virtualProviderService a virtual provider service to be unregistered
+     */
+    void unregisterProviderService(NetworkId networkId,
+                                   VirtualProviderService virtualProviderService);
+
+    /**
+     * Returns a set of currently registered virtual provider identities.
+     *
+     * @return set of virtual provider identifiers
+     */
+    Set<ProviderId> getProviders();
+
+    /**
+     * Returns a set of currently registered virtual provider identities
+     * corresponding to the requested providerService.
+     *
+     * @param virtualProviderService a virtual provider service
+     * @return set of virtual provider identifiers
+     */
+    Set<ProviderId> getProvidersByService(VirtualProviderService virtualProviderService);
+
+    /**
+     * Returns the virtual provider registered with the specified provider ID or null
+     * if none is found for the given provider family and default fall-back is
+     * not supported.
+     *
+     * @param providerId provider identifier
+     * @return provider
+     */
+    VirtualProvider getProvider(ProviderId providerId);
+
+    /**
+     * Returns the virtual provider for the specified device ID based on URI scheme.
+     *
+     * @param deviceId virtual device identifier
+     * @return provider bound to the URI scheme
+     */
+    VirtualProvider getProvider(DeviceId deviceId);
+
+    /**
+     * Returns the virtual provider registered with the specified scheme.
+     *
+     * @param scheme provider scheme
+     * @return provider
+     */
+    VirtualProvider getProvider(String scheme);
+
+    /**
+     * Returns a virtual provider service corresponding to
+     * the virtual network and provider class type.
+     *
+     * @param networkId a virtual network identifier
+     * @param providerClass a type of virtual provider
+     * @return a virtual provider service
+     */
+    VirtualProviderService getProviderService(NetworkId networkId,
+                                              Class<? extends VirtualProvider> providerClass);
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProviderService.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProviderService.java
new file mode 100644
index 0000000..8ad378f
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/VirtualProviderService.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.provider;
+
+/**
+ * Abstraction of a service through which virtual providers can inject information
+ * about the network environment into the virtual core.
+ *
+ * @param <P> type of the information virtual provider
+ */
+
+public interface VirtualProviderService<P extends VirtualProvider> {
+    /**
+     * Returns the virtual provider to which this service has been issued.
+     *
+     * @return virtual provider to which this service has been assigned
+     */
+    P provider();
+}
diff --git a/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/package-info.java b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/package-info.java
new file mode 100644
index 0000000..8d360d5
--- /dev/null
+++ b/apps/virtual/api/src/main/java/org/onosproject/incubator/net/virtual/provider/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Network virtualization provider interfaces and implementation.
+ */
+package org.onosproject.incubator.net.virtual.provider;
\ No newline at end of file
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/ComparatorsTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/ComparatorsTest.java
new file mode 100644
index 0000000..ba208bf
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/ComparatorsTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TenantId;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.*;
+import static org.onosproject.incubator.net.virtual.Comparators.VIRTUAL_DEVICE_COMPARATOR;
+import static org.onosproject.incubator.net.virtual.Comparators.VIRTUAL_NETWORK_COMPARATOR;
+import static org.onosproject.incubator.net.virtual.Comparators.VIRTUAL_PORT_COMPARATOR;
+import static org.onosproject.net.DeviceId.deviceId;
+
+public class ComparatorsTest {
+    private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final DeviceId DID = deviceId("of:foo");
+    private static final String MFR = "whitebox";
+    private static final String HW = "1.1.x";
+    private static final String SW = "3.9.1";
+    private static final String SN = "43311-12345";
+    private static final ChassisId CID = new ChassisId();
+
+    @Test
+    public void testVirtualNetworkComparator() {
+        assertNotEquals(0, VIRTUAL_NETWORK_COMPARATOR.compare(network(10, "tenantID"), network(10, "tenantID1")));
+        assertNotEquals(0, VIRTUAL_NETWORK_COMPARATOR.compare(network(10, "tenantID"), network(15, "tenantID1")));
+        assertNotEquals(0, VIRTUAL_NETWORK_COMPARATOR.compare(network(15, "tenantID1"), network(10, "tenantID1")));
+        assertNotEquals(0, VIRTUAL_NETWORK_COMPARATOR.compare(network(15, "tenantID"), network(10, "tenantID1")));
+    }
+
+    private VirtualNetwork network(int networkID, String tenantID) {
+        return new DefaultVirtualNetwork(NetworkId.networkId(networkID), TenantId.tenantId(tenantID));
+    }
+
+    @Test
+    public void testVirtualDeviceComparator() {
+        assertEquals(0, VIRTUAL_DEVICE_COMPARATOR.compare(vd(0, "of:foo"), vd(0, "of:foo")));
+        assertEquals(0, VIRTUAL_DEVICE_COMPARATOR.compare(vd(3, "of:foo"), vd(0, "of:foo")));
+        assertNotEquals(0, VIRTUAL_DEVICE_COMPARATOR.compare(vd(0, "of:bar"), vd(0, "of:foo")));
+        assertNotEquals(0, VIRTUAL_DEVICE_COMPARATOR.compare(vd(3, "of:bar"), vd(0, "of:foo")));
+    }
+
+    private VirtualDevice vd(int netID, String devID) {
+        return new DefaultVirtualDevice(NetworkId.networkId(netID), DeviceId.deviceId(devID));
+    }
+
+    @Test
+    public void testVirtualPortComparator() {
+        assertEquals(0, VIRTUAL_PORT_COMPARATOR.compare(vPort(2), vPort(2)));
+        assertEquals(4, VIRTUAL_PORT_COMPARATOR.compare(vPort(900), vPort(5)));
+        assertEquals(-8, VIRTUAL_PORT_COMPARATOR.compare(vPort(0), vPort(8)));
+    }
+
+    private VirtualPort vPort(int portNumber) {
+        return new DefaultVirtualPort(NetworkId.networkId(20),
+                                      new DefaultDevice(PID, DID, null, MFR, HW, SW, SN, CID),
+                                      PortNumber.portNumber(portNumber),
+                                      new ConnectPoint(DID, PortNumber.portNumber(900)));
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualDeviceTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualDeviceTest.java
new file mode 100644
index 0000000..22be664
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualDeviceTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.TestDeviceParams;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Test of the default virtual device model entity.
+ */
+public class DefaultVirtualDeviceTest extends TestDeviceParams {
+
+    /**
+     * Checks that the DefaultVirtualDevice class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultVirtualDevice.class);
+    }
+
+    @Test
+    public void testEquality() {
+        DefaultVirtualDevice device1 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device2 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device3 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID2);
+        DefaultVirtualDevice device4 =
+                new DefaultVirtualDevice(NetworkId.networkId(1), DID1);
+
+        new EqualsTester().addEqualityGroup(device1, device2).addEqualityGroup(device3)
+                .addEqualityGroup(device4).testEquals();
+    }
+}
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualHostTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualHostTest.java
new file mode 100644
index 0000000..7fcf70d
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualHostTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.TestDeviceParams;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Test of the default virtual host model entity.
+ */
+public class DefaultVirtualHostTest extends TestDeviceParams {
+
+    /**
+     * Checks that the DefaultVirtualHost class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultVirtualHost.class);
+    }
+
+    /**
+     * Tests the DefaultVirtualHost equality method.
+     */
+    @Test
+    public void testEquality() {
+        DefaultVirtualHost host1 =
+                new DefaultVirtualHost(NetworkId.networkId(0), HID1, MAC1, VLAN1, LOC1, IPSET1);
+        DefaultVirtualHost host2 =
+                new DefaultVirtualHost(NetworkId.networkId(0), HID1, MAC1, VLAN1, LOC1, IPSET1);
+        DefaultVirtualHost host3 =
+                new DefaultVirtualHost(NetworkId.networkId(0), HID2, MAC1, VLAN1, LOC1, IPSET1);
+        DefaultVirtualHost host4 =
+                new DefaultVirtualHost(NetworkId.networkId(1), HID2, MAC1, VLAN1, LOC1, IPSET1);
+
+        new EqualsTester().addEqualityGroup(host1, host2).addEqualityGroup(host3)
+                .addEqualityGroup(host4).testEquals();
+    }
+}
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualLinkTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualLinkTest.java
new file mode 100644
index 0000000..b96290f
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualLinkTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Test of the default virtual link model entity.
+ */
+public class DefaultVirtualLinkTest extends TestDeviceParams {
+
+    /**
+     * Checks that the DefaultVirtualLink class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultVirtualLink.class);
+    }
+
+    /**
+     * Tests the DefaultVirtualLink Builder to ensure that the src cannot be null.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testBuilderNullSrc() {
+        DefaultVirtualDevice device1 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device2 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID2);
+        ConnectPoint src = new ConnectPoint(device1.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(device2.id(), PortNumber.portNumber(2));
+
+        DefaultVirtualLink.builder()
+                .src(null)
+                .build();
+    }
+
+    /**
+     * Tests the DefaultVirtualLink Builder to ensure that the dst cannot be null.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testBuilderNullDst() {
+        DefaultVirtualDevice device1 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device2 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID2);
+        ConnectPoint src = new ConnectPoint(device1.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(device2.id(), PortNumber.portNumber(2));
+
+        DefaultVirtualLink.builder()
+                .dst(null)
+                .build();
+    }
+
+    /**
+     * Tests the DefaultVirtualLink Builder to ensure that the networkId cannot be null.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testBuilderNullNetworkId() {
+        DefaultVirtualDevice device1 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device2 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID2);
+        ConnectPoint src = new ConnectPoint(device1.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(device2.id(), PortNumber.portNumber(2));
+
+        DefaultVirtualLink.builder()
+                .networkId(null)
+                .build();
+    }
+
+    /**
+     * Tests the DefaultVirtualLink equality method.
+     */
+    @Test
+    public void testEquality() {
+        DefaultVirtualDevice device1 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device2 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID2);
+        ConnectPoint src = new ConnectPoint(device1.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(device2.id(), PortNumber.portNumber(2));
+
+        VirtualLink link1 = DefaultVirtualLink.builder()
+                .networkId(NetworkId.networkId(0))
+                .src(src)
+                .dst(dst)
+                .tunnelId(TunnelId.valueOf("1"))
+                .build();
+        VirtualLink link2 = DefaultVirtualLink.builder()
+                .networkId(NetworkId.networkId(0))
+                .src(src)
+                .dst(dst)
+                .tunnelId(TunnelId.valueOf("1"))
+                .build();
+        VirtualLink link3 = DefaultVirtualLink.builder()
+                .networkId(NetworkId.networkId(0))
+                .src(src)
+                .dst(dst)
+                .tunnelId(TunnelId.valueOf("2"))
+                .build();
+        VirtualLink link4 = DefaultVirtualLink.builder()
+                .networkId(NetworkId.networkId(1))
+                .src(src)
+                .dst(dst)
+                .tunnelId(TunnelId.valueOf("3"))
+                .build();
+
+        new EqualsTester().addEqualityGroup(link1, link2).addEqualityGroup(link3)
+                .addEqualityGroup(link4).testEquals();
+    }
+}
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualNetworkTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualNetworkTest.java
new file mode 100644
index 0000000..7ff4715
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualNetworkTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.TenantId;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Test of the default virtual network model entity.
+ */
+public class DefaultVirtualNetworkTest {
+    final String tenantIdValue1 = "TENANT_ID1";
+    final String tenantIdValue2 = "TENANT_ID2";
+
+    /**
+     * Checks that the DefaultVirtualNetwork class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultVirtualNetwork.class);
+    }
+
+    @Test
+    public void testEquality() {
+        DefaultVirtualNetwork network1 =
+                new DefaultVirtualNetwork(NetworkId.networkId(0), TenantId.tenantId(tenantIdValue1));
+        DefaultVirtualNetwork network2 =
+                new DefaultVirtualNetwork(NetworkId.networkId(0), TenantId.tenantId(tenantIdValue1));
+        DefaultVirtualNetwork network3 =
+                new DefaultVirtualNetwork(NetworkId.networkId(0), TenantId.tenantId(tenantIdValue2));
+        DefaultVirtualNetwork network4 =
+                new DefaultVirtualNetwork(NetworkId.networkId(1), TenantId.tenantId(tenantIdValue2));
+
+        new EqualsTester().addEqualityGroup(network1, network2).addEqualityGroup(network3)
+                .addEqualityGroup(network4).testEquals();
+    }
+}
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualPortTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualPortTest.java
new file mode 100644
index 0000000..61ba611
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/DefaultVirtualPortTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Test of the default virtual port model entity.
+ */
+public class DefaultVirtualPortTest extends TestDeviceParams {
+    /**
+     * Checks that the DefaultVirtualPort class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(DefaultVirtualPort.class);
+    }
+
+    @Test
+    public void testEquality() {
+        DefaultVirtualDevice device1 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID1);
+        DefaultVirtualDevice device2 =
+                new DefaultVirtualDevice(NetworkId.networkId(0), DID2);
+
+        ConnectPoint cpA = new ConnectPoint(device1.id(), PortNumber.portNumber(1));
+        ConnectPoint cpB = new ConnectPoint(device1.id(), PortNumber.portNumber(2));
+        ConnectPoint cpC = new ConnectPoint(device2.id(), PortNumber.portNumber(2));
+
+        DefaultVirtualPort port1 =
+                new DefaultVirtualPort(NetworkId.networkId(0), device1,
+                                       PortNumber.portNumber(1), cpA);
+        DefaultVirtualPort port2 =
+                new DefaultVirtualPort(NetworkId.networkId(0), device1,
+                                       PortNumber.portNumber(1), cpA);
+        DefaultVirtualPort port3 =
+                new DefaultVirtualPort(NetworkId.networkId(0), device1,
+                                       PortNumber.portNumber(2), cpB);
+        DefaultVirtualPort port4 =
+                new DefaultVirtualPort(NetworkId.networkId(1), device2,
+                                       PortNumber.portNumber(2), cpC);
+
+
+        new EqualsTester().addEqualityGroup(port1, port2).addEqualityGroup(port3)
+                .addEqualityGroup(port4).testEquals();
+    }
+}
diff --git a/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/codec/VirtualHostCodecTest.java b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/codec/VirtualHostCodecTest.java
new file mode 100644
index 0000000..043e434
--- /dev/null
+++ b/apps/virtual/api/src/test/java/org/onosproject/incubator/net/virtual/codec/VirtualHostCodecTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.codec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+import java.util.stream.IntStream;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.MockCodecContext;
+import org.onosproject.incubator.net.virtual.DefaultVirtualHost;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.NetTestTools;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isOneOf;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Tests VirtualHostCodec class.
+ */
+@Ignore
+public class VirtualHostCodecTest {
+
+    private static final String TEST_IP1 = "1.1.1.1";
+    private static final String TEST_IP2 = "2.2.2.2";
+    private static final String TEST_HOST_ID = "12:34:56:78:90:11/1";
+    private static final String TEST_MAC_ADDRESS = "11:11:22:22:33:33";
+    private static final long TEST_NETWORK_ID = 44L;
+    private static final short TEST_VLAN_ID = (short) 12;
+    private static final ConnectPoint CONNECT_POINT =
+            NetTestTools.connectPoint("d1", 1);
+
+    @Test
+    public void testEncode() {
+        MockCodecContext context = new MockCodecContext();
+        NetworkId networkId = NetworkId.networkId(TEST_NETWORK_ID);
+        HostId id = NetTestTools.hid(TEST_HOST_ID);
+        MacAddress mac = MacAddress.valueOf(TEST_MAC_ADDRESS);
+        VlanId vlan = VlanId.vlanId(TEST_VLAN_ID);
+        HostLocation location =
+                new HostLocation(CONNECT_POINT, 0L);
+        Set<IpAddress> ips = ImmutableSet.of(IpAddress.valueOf(TEST_IP1),
+                                             IpAddress.valueOf(TEST_IP2));
+        VirtualHost host =
+                new DefaultVirtualHost(networkId, id, mac, vlan, location, ips);
+        JsonCodec<VirtualHost> codec = context.codec(VirtualHost.class);
+        ObjectNode node = codec.encode(host, context);
+
+        assertThat(node.get(VirtualHostCodec.NETWORK_ID).asLong(),
+                   is(TEST_NETWORK_ID));
+        assertThat(node.get(VirtualHostCodec.HOST_ID).asText(),
+                   is(TEST_HOST_ID));
+        assertThat(node.get(VirtualHostCodec.MAC_ADDRESS).asText(),
+                   is(TEST_MAC_ADDRESS));
+        assertThat(node.get(VirtualHostCodec.VLAN).asInt(),
+                   is((int) TEST_VLAN_ID));
+        assertThat(node.get(VirtualHostCodec.HOST_LOCATION).get(0).get("elementId").asText(),
+                   is(location.deviceId().toString()));
+        assertThat(node.get(VirtualHostCodec.HOST_LOCATION).get(0).get("port").asLong(),
+                   is(location.port().toLong()));
+
+        JsonNode jsonIps = node.get(VirtualHostCodec.IP_ADDRESSES);
+        assertThat(jsonIps, notNullValue());
+        assertThat(jsonIps.isArray(), is(true));
+        assertThat(jsonIps.size(), is(ips.size()));
+
+        IntStream.of(0, 1).forEach(index ->
+            assertThat(jsonIps.get(index).asText(),
+                       isOneOf(TEST_IP1, TEST_IP2)));
+    }
+
+    @Test
+    public void testDecode() throws IOException {
+        MockCodecContext context = new MockCodecContext();
+        InputStream jsonStream =
+                VirtualHostCodecTest.class.getResourceAsStream("VirtualHost.json");
+        JsonNode json = context.mapper().readTree(jsonStream);
+        assertThat(json, notNullValue());
+        JsonCodec<VirtualHost> codec = context.codec(VirtualHost.class);
+        VirtualHost virtualHost = codec.decode((ObjectNode) json, context);
+        assertThat(virtualHost, notNullValue());
+
+        assertThat(virtualHost.networkId().id(),
+                   is(TEST_NETWORK_ID));
+        assertThat(virtualHost.id().toString(),
+                   is(NetTestTools.hid(TEST_MAC_ADDRESS + "/12").toString()));
+        assertThat(virtualHost.mac().toString(),
+                   is(TEST_MAC_ADDRESS));
+        assertThat(virtualHost.vlan().id(),
+                   is((short) TEST_VLAN_ID));
+        assertThat(virtualHost.location().deviceId(),
+                   is(CONNECT_POINT.deviceId()));
+        assertThat(virtualHost.location().port().toLong(),
+                   is(CONNECT_POINT.port().toLong()));
+
+
+        assertThat(virtualHost.ipAddresses().contains(IpAddress.valueOf(TEST_IP1)),
+                   is(true));
+        assertThat(virtualHost.ipAddresses().contains(IpAddress.valueOf(TEST_IP2)),
+                   is(true));
+    }
+}
diff --git a/apps/virtual/api/src/test/resources/org/onosproject/incubator/net/virtual/codec/VirtualHost.json b/apps/virtual/api/src/test/resources/org/onosproject/incubator/net/virtual/codec/VirtualHost.json
new file mode 100644
index 0000000..818ce0b
--- /dev/null
+++ b/apps/virtual/api/src/test/resources/org/onosproject/incubator/net/virtual/codec/VirtualHost.json
@@ -0,0 +1,15 @@
+{
+  "networkId": "44",
+  "mac": "11:11:22:22:33:33",
+  "vlan": "12",
+  "ipAddresses": [
+    "1.1.1.1",
+    "2.2.2.2"
+  ],
+  "locations": [
+    {
+      "elementId": "of:d1",
+      "port": "1"
+    }
+  ]
+}
diff --git a/apps/virtual/app/BUILD b/apps/virtual/app/BUILD
new file mode 100644
index 0000000..15941f8
--- /dev/null
+++ b/apps/virtual/app/BUILD
@@ -0,0 +1,26 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + REST + CLI + [
+    "@metrics_core//jar",
+    "//core/common:onos-core-common",
+    "//core/net:onos-core-net",
+    "//core/store/serializers:onos-core-serializers",
+    "//apps/tunnel/api:onos-apps-tunnel-api",
+    "//apps/virtual/api:onos-apps-virtual-api",
+]
+
+TEST_DEPS = TEST_ADAPTERS + [
+    "@minimal_json//jar",
+    "@jersey_client//jar",
+    "//web/api:onos-rest-tests",
+    "//utils/osgi:onlab-osgi-tests",
+]
+
+osgi_jar_with_tests(
+    api_description = "REST API for Virtual Networks",
+    api_package = "org.onosproject.incubator.net.virtual.rest",
+    api_title = "Virtual Networks",
+    api_version = "1.0",
+    karaf_command_packages = ["org.onosproject.incubator.net.virtual.cli"],
+    test_deps = TEST_DEPS,
+    visibility = ["//visibility:public"],
+    deps = COMPILE_DEPS,
+)
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantAddCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantAddCommand.java
new file mode 100644
index 0000000..a449afb
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantAddCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Creates a new virtual network tenant.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-add-tenant",
+        description = "Creates a new virtual network tenant.")
+
+public class TenantAddCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "id", description = "Tenant ID",
+            required = true, multiValued = false)
+    String id = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.registerTenantId(TenantId.tenantId(id));
+        print("Tenant successfully added.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantCompleter.java
new file mode 100644
index 0000000..29a625c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantCompleter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Tenant Id completer.
+ */
+@Service
+public class TenantCompleter implements Completer {
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+
+        // Fetch our service and feed it's offerings to the string completer
+        VirtualNetworkAdminService service = AbstractShellCommand.get(VirtualNetworkAdminService.class);
+
+        SortedSet<String> strings = delegate.getStrings();
+
+        for (TenantId tenantId : service.getTenantIds()) {
+            strings.add(tenantId.id());
+        }
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(session, commandLine, candidates);
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantListCommand.java
new file mode 100644
index 0000000..6590090
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantListCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.utils.Comparators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all tenants.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-tenants",
+        description = "Lists all virtual network tenants.")
+public class TenantListCommand extends AbstractShellCommand {
+
+    private static final String FMT_TENANT = "tenantId=%s";
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        List<TenantId> tenants = new ArrayList<>();
+        tenants.addAll(service.getTenantIds());
+        Collections.sort(tenants, Comparators.TENANT_ID_COMPARATOR);
+
+        tenants.forEach(this::printTenant);
+    }
+
+    private void printTenant(TenantId tenantId) {
+        print(FMT_TENANT, tenantId.id());
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantRemoveCommand.java
new file mode 100644
index 0000000..2877f59
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/TenantRemoveCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Creates a new virtual network tenant.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-tenant",
+        description = "Removes a virtual network tenant.")
+
+public class TenantRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "id", description = "Tenant ID",
+            required = true, multiValued = false)
+    @Completion(TenantCompleter.class)
+    String id = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.unregisterTenantId(TenantId.tenantId(id));
+        print("Tenant successfully removed.");
+    }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCompleter.java
new file mode 100644
index 0000000..410b483
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCompleter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Virtual device completer.
+ *
+ * Assumes the first argument which can be parsed to a number is network id.
+ */
+@Service
+public class VirtualDeviceCompleter extends AbstractChoicesCompleter {
+    @Override
+    protected List<String> choices() {
+        //parse argument list for network id
+        String[] argsArray = commandLine.getArguments();
+        for (String str : argsArray) {
+            if (str.matches("[0-9]+")) {
+                long networkId = Long.valueOf(str);
+                return getSortedVirtualDevices(networkId).stream()
+                        .map(virtualDevice -> virtualDevice.id().toString())
+                        .collect(Collectors.toList());
+            }
+        }
+        return Collections.singletonList("Missing network id");
+    }
+
+    /**
+     * Returns the list of virtual devices sorted using the network identifier.
+     *
+     * @param networkId network id
+     * @return sorted virtual device list
+     */
+    private List<VirtualDevice> getSortedVirtualDevices(long networkId) {
+        VirtualNetworkService service = getService(VirtualNetworkService.class);
+
+        List<VirtualDevice> virtualDevices = new ArrayList<>();
+        virtualDevices.addAll(service.getVirtualDevices(NetworkId.networkId(networkId)));
+        Collections.sort(virtualDevices, Comparators.VIRTUAL_DEVICE_COMPARATOR);
+        return virtualDevices;
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCreateCommand.java
new file mode 100644
index 0000000..187c0e1
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceCreateCommand.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Creates a new virtual device.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-device",
+        description = "Creates a new virtual device in a network.")
+public class VirtualDeviceCreateCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Device ID",
+            required = true, multiValued = false)
+    String deviceId = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.createVirtualDevice(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId));
+        print("Virtual device successfully created.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceListCommand.java
new file mode 100644
index 0000000..4a4acfc
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceListCommand.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all virtual devices for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-devices",
+        description = "Lists all virtual devices in a virtual network.")
+public class VirtualDeviceListCommand extends AbstractShellCommand {
+
+    private static final String FMT_VIRTUAL_DEVICE =
+            "deviceId=%s";
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Override
+    protected void doExecute() {
+
+        getSortedVirtualDevices().forEach(this::printVirtualDevice);
+    }
+
+    /**
+     * Returns the list of virtual devices sorted using the device identifier.
+     *
+     * @return sorted virtual device list
+     */
+    private List<VirtualDevice> getSortedVirtualDevices() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+
+        List<VirtualDevice> virtualDevices = new ArrayList<>();
+        virtualDevices.addAll(service.getVirtualDevices(NetworkId.networkId(networkId)));
+        Collections.sort(virtualDevices, Comparators.VIRTUAL_DEVICE_COMPARATOR);
+        return virtualDevices;
+    }
+
+    /**
+     * Prints out each virtual device.
+     *
+     * @param virtualDevice virtual device
+     */
+    private void printVirtualDevice(VirtualDevice virtualDevice) {
+        print(FMT_VIRTUAL_DEVICE, virtualDevice.id());
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceRemoveCommand.java
new file mode 100644
index 0000000..f6213c7
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualDeviceRemoveCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.DeviceIdCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Removes a virtual device.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-device",
+        description = "Removes a virtual device.")
+public class VirtualDeviceRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Device ID",
+            required = true, multiValued = false)
+    @Completion(DeviceIdCompleter.class)
+    String deviceId = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.removeVirtualDevice(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId));
+        print("Virtual device successfully removed.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualFlowsListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualFlowsListCommand.java
new file mode 100644
index 0000000..05da804
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualFlowsListCommand.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onlab.util.StringFilter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.PlaceholderCompleter;
+import org.onosproject.cli.net.FlowRuleStatusCompleter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowEntry.FlowEntryState;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.utils.Comparators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+
+/**
+ * Lists all currently-known flows.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-flows",
+         description = "Lists all currently-known flows for a virtual network.")
+public class VirtualFlowsListCommand extends AbstractShellCommand {
+
+    private static final Predicate<FlowEntry> TRUE_PREDICATE = f -> true;
+
+    public static final String ANY = "any";
+
+    private static final String LONG_FORMAT = "    id=%s, state=%s, bytes=%s, "
+            + "packets=%s, duration=%s, liveType=%s, priority=%s, tableId=%s, appId=%s, "
+            + "payLoad=%s, selector=%s, treatment=%s";
+
+    private static final String SHORT_FORMAT = "    %s, bytes=%s, packets=%s, "
+            + "table=%s, priority=%s, selector=%s, treatment=%s";
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "state", description = "Flow Rule state",
+            required = false, multiValued = false)
+    @Completion(FlowRuleStatusCompleter.class)
+    String state = null;
+
+    @Argument(index = 2, name = "uri", description = "Device ID",
+              required = false, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String uri = null;
+
+    @Argument(index = 3, name = "table", description = "Table ID",
+            required = false, multiValued = false)
+    @Completion(PlaceholderCompleter.class)
+    String table = null;
+
+    @Option(name = "-s", aliases = "--short",
+            description = "Print more succinct output for each flow",
+            required = false, multiValued = false)
+    private boolean shortOutput = false;
+
+    @Option(name = "-c", aliases = "--count",
+            description = "Print flow count only",
+            required = false, multiValued = false)
+    private boolean countOnly = false;
+
+    @Option(name = "-f", aliases = "--filter",
+            description = "Filter flows by specific key",
+            required = false, multiValued = true)
+    private List<String> filter = new ArrayList<>();
+
+    private Predicate<FlowEntry> predicate = TRUE_PREDICATE;
+
+    private StringFilter contentFilter;
+
+    @Override
+    protected void doExecute() {
+        CoreService coreService = get(CoreService.class);
+
+        VirtualNetworkService vnetservice = get(VirtualNetworkService.class);
+        DeviceService deviceService = vnetservice.get(NetworkId.networkId(networkId),
+                                                      DeviceService.class);
+        FlowRuleService service = vnetservice.get(NetworkId.networkId(networkId),
+                                                  FlowRuleService.class);
+        contentFilter = new StringFilter(filter, StringFilter.Strategy.AND);
+
+        compilePredicate();
+
+        SortedMap<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service);
+
+        if (outputJson()) {
+            print("%s", json(flows.keySet(), flows));
+        } else {
+            flows.forEach((device, flow) -> printFlows(device, flow, coreService));
+        }
+    }
+
+    /**
+     * Produces a JSON array of flows grouped by the each device.
+     *
+     * @param devices     collection of devices to group flow by
+     * @param flows       collection of flows per each device
+     * @return JSON array
+     */
+    private JsonNode json(Iterable<Device> devices,
+                          Map<Device, List<FlowEntry>> flows) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+        for (Device device : devices) {
+            result.add(json(mapper, device, flows.get(device)));
+        }
+        return result;
+    }
+
+    /**
+     * Compiles a predicate to find matching flows based on the command
+     * arguments.
+     */
+    private void compilePredicate() {
+        if (state != null && !state.equals(ANY)) {
+            final FlowEntryState feState = FlowEntryState.valueOf(state.toUpperCase());
+            predicate = predicate.and(f -> f.state().equals(feState));
+        }
+
+        if (table != null) {
+            final int tableId = Integer.parseInt(table);
+            predicate = predicate.and(f -> f.tableId() == tableId);
+        }
+    }
+
+    // Produces JSON object with the flows of the given device.
+    private ObjectNode json(ObjectMapper mapper,
+                            Device device, List<FlowEntry> flows) {
+        ObjectNode result = mapper.createObjectNode();
+        ArrayNode array = mapper.createArrayNode();
+
+        flows.forEach(flow -> array.add(jsonForEntity(flow, FlowEntry.class)));
+
+        result.put("device", device.id().toString())
+                .put("flowCount", flows.size())
+                .set("flows", array);
+        return result;
+    }
+
+    /**
+     * Returns the list of devices sorted using the device ID URIs.
+     *
+     * @param deviceService device service
+     * @param service flow rule service
+     * @return sorted device list
+     */
+    protected SortedMap<Device, List<FlowEntry>> getSortedFlows(DeviceService deviceService,
+                                                          FlowRuleService service) {
+        SortedMap<Device, List<FlowEntry>> flows = new TreeMap<>(Comparators.ELEMENT_COMPARATOR);
+        List<FlowEntry> rules;
+
+        Iterable<Device> devices = null;
+        if (uri == null) {
+            devices = deviceService.getDevices();
+        } else {
+            Device dev = deviceService.getDevice(DeviceId.deviceId(uri));
+            devices = (dev == null) ? deviceService.getDevices()
+                                    : Collections.singletonList(dev);
+        }
+
+        for (Device d : devices) {
+            if (predicate.equals(TRUE_PREDICATE)) {
+                rules = newArrayList(service.getFlowEntries(d.id()));
+            } else {
+                rules = newArrayList();
+                for (FlowEntry f : service.getFlowEntries(d.id())) {
+                    if (predicate.test(f)) {
+                        rules.add(f);
+                    }
+                }
+            }
+            rules.sort(Comparators.FLOW_RULE_COMPARATOR);
+
+            flows.put(d, rules);
+        }
+        return flows;
+    }
+
+    /**
+     * Prints flows.
+     *
+     * @param d     the device
+     * @param flows the set of flows for that device
+     * @param coreService core system service
+     */
+    protected void printFlows(Device d, List<FlowEntry> flows,
+                              CoreService coreService) {
+        List<FlowEntry> filteredFlows = flows.stream().
+                filter(f -> contentFilter.filter(f)).collect(Collectors.toList());
+        boolean empty = filteredFlows == null || filteredFlows.isEmpty();
+        print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : filteredFlows.size());
+        if (empty || countOnly) {
+            return;
+        }
+
+        for (FlowEntry f : filteredFlows) {
+            if (shortOutput) {
+                print(SHORT_FORMAT, f.state(), f.bytes(), f.packets(),
+                        f.tableId(), f.priority(), f.selector().criteria(),
+                        printTreatment(f.treatment()));
+            } else {
+                ApplicationId appId = coreService.getAppId(f.appId());
+                print(LONG_FORMAT, Long.toHexString(f.id().value()), f.state(),
+                        f.bytes(), f.packets(), f.life(), f.liveType(), f.priority(), f.tableId(),
+                        appId != null ? appId.name() : "<none>",
+                        f.payLoad() == null ? null : Arrays.toString(f.payLoad().payLoad()),
+                        f.selector().criteria(), f.treatment());
+            }
+        }
+    }
+
+    private String printTreatment(TrafficTreatment treatment) {
+        final String delimiter = ", ";
+        StringBuilder builder = new StringBuilder("[");
+        if (!treatment.immediate().isEmpty()) {
+            builder.append("immediate=" + treatment.immediate() + delimiter);
+        }
+        if (!treatment.deferred().isEmpty()) {
+            builder.append("deferred=" + treatment.deferred() + delimiter);
+        }
+        if (treatment.clearedDeferred()) {
+            builder.append("clearDeferred" + delimiter);
+        }
+        if (treatment.tableTransition() != null) {
+            builder.append("transition=" + treatment.tableTransition() + delimiter);
+        }
+        if (treatment.metered() != null) {
+            builder.append("meter=" + treatment.metered() + delimiter);
+        }
+        if (treatment.writeMetadata() != null) {
+            builder.append("metadata=" + treatment.writeMetadata() + delimiter);
+        }
+        // Chop off last delimiter
+        builder.replace(builder.length() - delimiter.length(), builder.length(), "");
+        builder.append("]");
+        return builder.toString();
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCompleter.java
new file mode 100644
index 0000000..c8f6c44
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCompleter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+/**
+ * Virtual host completer.
+ *
+ * Assumes the first argument which can be parsed to a number is network id.
+ */
+public class VirtualHostCompleter extends AbstractChoicesCompleter {
+    @Override
+    protected List<String> choices() {
+        //parse argument list for network id
+        String[] argsArray = commandLine.getArguments();
+        for (String str : argsArray) {
+            if (str.matches("[0-9]+")) {
+                long networkId = Long.valueOf(str);
+                return getSortedVirtualHosts(networkId).stream()
+                        .map(virtualHost -> virtualHost.id().toString())
+                        .collect(Collectors.toList());
+            }
+        }
+        return Collections.singletonList("Missing network id");
+    }
+
+    /**
+     * Returns the list of virtual hosts sorted using the host identifier.
+     *
+     * @param networkId network id
+     * @return virtual host list
+     */
+    private List<VirtualHost> getSortedVirtualHosts(long networkId) {
+        VirtualNetworkService service = getService(VirtualNetworkService.class);
+
+        List<VirtualHost> virtualHosts = new ArrayList<>();
+        virtualHosts.addAll(service.getVirtualHosts(NetworkId.networkId(networkId)));
+        return virtualHosts;
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCreateCommand.java
new file mode 100644
index 0000000..da198b1
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostCreateCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Creates a new virtual host.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-host",
+        description = "Creates a new virtual host in a network.")
+public class VirtualHostCreateCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "mac", description = "Mac address",
+            required = true, multiValued = false)
+    String mac = null;
+
+    @Argument(index = 2, name = "vlan", description = "Vlan",
+            required = true, multiValued = false)
+    short vlan;
+
+    @Argument(index = 3, name = "hostLocationDeviceId", description = "Host location device ID",
+            required = true, multiValued = false)
+    String hostLocationDeviceId;
+
+    @Argument(index = 4, name = "hostLocationPortNumber", description = "Host location port number",
+            required = true, multiValued = false)
+    long hostLocationPortNumber;
+
+    // ip addresses
+    @Option(name = "--hostIp", description = "Host IP addresses.  Can be specified multiple times.",
+            required = false, multiValued = true)
+    protected String[] hostIpStrings;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+
+        Set<IpAddress> hostIps = new HashSet<>();
+        if (hostIpStrings != null) {
+            Arrays.stream(hostIpStrings).forEach(s -> hostIps.add(IpAddress.valueOf(s)));
+        }
+        HostLocation hostLocation = new HostLocation(DeviceId.deviceId(hostLocationDeviceId),
+                                                     PortNumber.portNumber(hostLocationPortNumber),
+                                                     System.currentTimeMillis());
+        MacAddress macAddress = MacAddress.valueOf(mac);
+        VlanId vlanId = VlanId.vlanId(vlan);
+        service.createVirtualHost(NetworkId.networkId(networkId),
+                                  HostId.hostId(macAddress, vlanId), macAddress, vlanId,
+                                  hostLocation, hostIps);
+        print("Virtual host successfully created.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostListCommand.java
new file mode 100644
index 0000000..38533ee
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostListCommand.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Lists all virtual hosts for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-hosts",
+        description = "Lists all virtual hosts in a virtual network.")
+public class VirtualHostListCommand extends AbstractShellCommand {
+
+    private static final String FMT_VIRTUAL_HOST =
+            "id=%s, mac=%s, vlan=%s, location=%s, ips=%s";
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Override
+    protected void doExecute() {
+        getSortedVirtualHosts().forEach(this::printVirtualHost);
+    }
+
+    /**
+     * Returns the list of virtual hosts sorted using the device identifier.
+     *
+     * @return virtual host list
+     */
+    private List<VirtualHost> getSortedVirtualHosts() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+
+        List<VirtualHost> virtualHosts = new ArrayList<>();
+        virtualHosts.addAll(service.getVirtualHosts(NetworkId.networkId(networkId)));
+        return virtualHosts;
+    }
+
+    /**
+     * Prints out each virtual host.
+     *
+     * @param virtualHost virtual host
+     */
+    private void printVirtualHost(VirtualHost virtualHost) {
+        print(FMT_VIRTUAL_HOST, virtualHost.id().toString(), virtualHost.mac().toString(),
+              virtualHost.vlan().toString(), virtualHost.location().toString(),
+              virtualHost.ipAddresses().toString());
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostRemoveCommand.java
new file mode 100644
index 0000000..329e4a3
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualHostRemoveCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.HostId;
+//import org.onosproject.net.HostId;
+
+/**
+ * Removes a virtual host.
+ */
+
+@Service
+@Command(scope = "onos", name = "vnet-remove-host",
+        description = "Removes a virtual host.")
+public class VirtualHostRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "id", description = "Host ID",
+              required = true, multiValued = false)
+    @Completion(VirtualHostCompleter.class)
+    String id = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.removeVirtualHost(NetworkId.networkId(networkId), HostId.hostId(id));
+        print("Virtual host successfully removed.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkCreateCommand.java
new file mode 100644
index 0000000..4bc6ea5
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkCreateCommand.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Creates a new virtual link.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-link",
+        description = "Creates a new virtual link in a network.")
+public class VirtualLinkCreateCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "srcDeviceId", description = "Source device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String srcDeviceId = null;
+
+    @Argument(index = 2, name = "srcPortNum", description = "Source port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer srcPortNum = null;
+
+    @Argument(index = 3, name = "dstDeviceId", description = "Destination device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String dstDeviceId = null;
+
+    @Argument(index = 4, name = "dstPortNum", description = "Destination port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer dstPortNum = null;
+
+    @Option(name = "-b", aliases = "--bidirectional",
+            description = "If this argument is passed in then the virtual link created will be bidirectional, " +
+                    "otherwise the link will be unidirectional.",
+            required = false, multiValued = false)
+    boolean bidirectional = false;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        ConnectPoint src = new ConnectPoint(DeviceId.deviceId(srcDeviceId), PortNumber.portNumber(srcPortNum));
+        ConnectPoint dst = new ConnectPoint(DeviceId.deviceId(dstDeviceId), PortNumber.portNumber(dstPortNum));
+
+        service.createVirtualLink(NetworkId.networkId(networkId), src, dst);
+        if (bidirectional) {
+            service.createVirtualLink(NetworkId.networkId(networkId), dst, src);
+        }
+        print("Virtual link successfully created.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkListCommand.java
new file mode 100644
index 0000000..a69e0b7
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkListCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Lists all virtual links for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-links",
+        description = "Lists all virtual links in a virtual network.")
+public class VirtualLinkListCommand extends AbstractShellCommand {
+
+    private static final String FMT_VIRTUAL_LINK =
+            "src=%s, dst=%s, state=%s, tunnelId=%s";
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Override
+    protected void doExecute() {
+
+        getSortedVirtualLinks().forEach(this::printVirtualLink);
+    }
+
+    /**
+     * Returns the list of virtual links sorted using the device identifier.
+     *
+     * @return virtual link list
+     */
+    private List<VirtualLink> getSortedVirtualLinks() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+
+        List<VirtualLink> virtualLinks = new ArrayList<>();
+        virtualLinks.addAll(service.getVirtualLinks(NetworkId.networkId(networkId)));
+        return virtualLinks;
+    }
+
+    /**
+     * Prints out each virtual link.
+     *
+     * @param virtualLink virtual link
+     */
+    private void printVirtualLink(VirtualLink virtualLink) {
+        print(FMT_VIRTUAL_LINK, virtualLink.src().toString(), virtualLink.dst().toString(),
+              virtualLink.state(),
+              virtualLink.tunnelId() == null ? null : virtualLink.tunnelId().toString());
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkRemoveCommand.java
new file mode 100644
index 0000000..5f613f4
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualLinkRemoveCommand.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Removes a virtual link.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-link",
+        description = "Removes a virtual link.")
+public class VirtualLinkRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "srcDeviceId", description = "Source device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String srcDeviceId = null;
+
+    @Argument(index = 2, name = "srcPortNum", description = "Source port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer srcPortNum = null;
+
+    @Argument(index = 3, name = "dstDeviceId", description = "Destination device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String dstDeviceId = null;
+
+    @Argument(index = 4, name = "dstPortNum", description = "Destination port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer dstPortNum = null;
+
+    @Option(name = "-b", aliases = "--bidirectional",
+            description = "If this argument is passed in then then bidirectional virtual link will be removed, " +
+                    "otherwise the unidirectional link will be removed.",
+            required = false, multiValued = false)
+    boolean bidirectional = false;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        ConnectPoint src = new ConnectPoint(DeviceId.deviceId(srcDeviceId), PortNumber.portNumber(srcPortNum));
+        ConnectPoint dst = new ConnectPoint(DeviceId.deviceId(dstDeviceId), PortNumber.portNumber(dstPortNum));
+
+        service.removeVirtualLink(NetworkId.networkId(networkId), src, dst);
+        if (bidirectional) {
+            service.removeVirtualLink(NetworkId.networkId(networkId), dst, src);
+        }
+        print("Virtual link successfully removed.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkBalanceMastersCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkBalanceMastersCommand.java
new file mode 100644
index 0000000..51a0c96
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkBalanceMastersCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.mastership.MastershipAdminService;
+
+/**
+ * Forces virtual network device mastership rebalancing.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-balance-masters",
+        description = "Forces virtual network device mastership rebalancing")
+public class VirtualNetworkBalanceMastersCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+    @Override
+    protected void doExecute() {
+        VirtualNetworkService vnetService = get(VirtualNetworkService.class);
+        MastershipAdminService mastershipAdminService = vnetService
+                .get(NetworkId.networkId(networkId), MastershipAdminService.class);
+        mastershipAdminService.balanceRoles();
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCompleter.java
new file mode 100644
index 0000000..1a3e7a2
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCompleter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.SortedSet;
+
+/**
+ * Virtual network completer.
+ */
+@Service
+public class VirtualNetworkCompleter implements Completer {
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+
+        // Fetch our service and feed it's offerings to the string completer
+        VirtualNetworkAdminService service = AbstractShellCommand.get(VirtualNetworkAdminService.class);
+
+        List<VirtualNetwork> virtualNetworks = new ArrayList<>();
+
+        Set<TenantId> tenantSet = service.getTenantIds();
+        tenantSet.forEach(tenantId -> virtualNetworks.addAll(service.getVirtualNetworks(tenantId)));
+
+        Collections.sort(virtualNetworks, Comparators.VIRTUAL_NETWORK_COMPARATOR);
+
+        SortedSet<String> strings = delegate.getStrings();
+        virtualNetworks.forEach(virtualNetwork -> strings.add(virtualNetwork.id().toString()));
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(session, commandLine, candidates);
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCreateCommand.java
new file mode 100644
index 0000000..d1ebe47
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkCreateCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Creates a new virtual network.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create",
+        description = "Creates a new virtual network.")
+public class VirtualNetworkCreateCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "id", description = "Tenant ID",
+            required = true, multiValued = false)
+    @Completion(TenantCompleter.class)
+    String id = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.createVirtualNetwork(TenantId.tenantId(id));
+        print("Virtual network successfully created.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentCreateCommand.java
new file mode 100644
index 0000000..4a8b400
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentCreateCommand.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.net.ConnectivityIntentCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+
+import java.util.List;
+
+/**
+ * Installs virtual network intents.
+ */
+@Service
+@Command(scope = "onos", name = "add-vnet-intent",
+        description = "Installs virtual network connectivity intent")
+public class VirtualNetworkIntentCreateCommand extends ConnectivityIntentCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "ingressDevice",
+            description = "Ingress Device/Port Description",
+            required = true, multiValued = false)
+    String ingressDeviceString = null;
+
+    @Argument(index = 2, name = "egressDevice",
+            description = "Egress Device/Port Description",
+            required = true, multiValued = false)
+    String egressDeviceString = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+        IntentService virtualNetworkIntentService = service.get(NetworkId.networkId(networkId), IntentService.class);
+
+        ConnectPoint ingress = ConnectPoint.deviceConnectPoint(ingressDeviceString);
+        ConnectPoint egress = ConnectPoint.deviceConnectPoint(egressDeviceString);
+
+        TrafficSelector selector = buildTrafficSelector();
+        TrafficTreatment treatment = buildTrafficTreatment();
+
+        List<Constraint> constraints = buildConstraints();
+
+        Intent intent = VirtualNetworkIntent.builder()
+                .networkId(NetworkId.networkId(networkId))
+                .appId(appId())
+                .key(key())
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(ingress)
+                .egressPoint(egress)
+                .constraints(constraints)
+                .priority(priority())
+                .build();
+        virtualNetworkIntentService.submit(intent);
+        print("Virtual intent submitted:\n%s", intent.toString());
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentRemoveCommand.java
new file mode 100644
index 0000000..eea3d45
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkIntentRemoveCommand.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+
+import java.math.BigInteger;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onosproject.net.intent.IntentState.FAILED;
+import static org.onosproject.net.intent.IntentState.WITHDRAWN;
+
+/**
+ * Removes a virtual network intent.
+ */
+@Service
+@Command(scope = "onos", name = "remove-vnet-intent",
+        description = "Removes the virtual network intent")
+public class VirtualNetworkIntentRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "app",
+            description = "Application ID",
+            required = false, multiValued = false)
+    String applicationIdString = null;
+
+    @Argument(index = 2, name = "key",
+            description = "Intent Key",
+            required = false, multiValued = false)
+    String keyString = null;
+
+    @Option(name = "-p", aliases = "--purge",
+            description = "Purge the intent from the store after removal",
+            required = false, multiValued = false)
+    private boolean purgeAfterRemove = false;
+
+    @Option(name = "-s", aliases = "--sync",
+            description = "Waits for the removal before returning",
+            required = false, multiValued = false)
+    private boolean sync = false;
+
+    private static final EnumSet<IntentState> CAN_PURGE = EnumSet.of(WITHDRAWN, FAILED);
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+        IntentService intentService = service.get(NetworkId.networkId(networkId), IntentService.class);
+        CoreService coreService = get(CoreService.class);
+
+        if (purgeAfterRemove || sync) {
+            print("Using \"sync\" to remove/purge intents - this may take a while...");
+            print("Check \"summary\" to see remove/purge progress.");
+        }
+
+        ApplicationId appId = appId();
+        if (!isNullOrEmpty(applicationIdString)) {
+            appId = coreService.getAppId(applicationIdString);
+            if (appId == null) {
+                print("Cannot find application Id %s", applicationIdString);
+                return;
+            }
+        }
+
+        if (isNullOrEmpty(keyString)) {
+            for (Intent intent : intentService.getIntents()) {
+                if (intent.appId().equals(appId)) {
+                    removeIntent(intentService, intent);
+                }
+            }
+
+        } else {
+            final Key key;
+            if (keyString.startsWith("0x")) {
+                // The intent uses a LongKey
+                keyString = keyString.replaceFirst("0x", "");
+                key = Key.of(new BigInteger(keyString, 16).longValue(), appId);
+            } else {
+                // The intent uses a StringKey
+                key = Key.of(keyString, appId);
+            }
+
+            Intent intent = intentService.getIntent(key);
+            if (intent != null) {
+                removeIntent(intentService, intent);
+            } else {
+                print("Intent not found!");
+            }
+        }
+    }
+
+    /**
+     * Removes the intent using the specified intentService.
+     *
+     * @param intentService intent service
+     * @param intent        intent
+     */
+    private void removeIntent(IntentService intentService, Intent intent) {
+        IntentListener listener = null;
+        Key key = intent.key();
+        final CountDownLatch withdrawLatch, purgeLatch;
+        if (purgeAfterRemove || sync) {
+            // set up latch and listener to track uninstall progress
+            withdrawLatch = new CountDownLatch(1);
+            purgeLatch = purgeAfterRemove ? new CountDownLatch(1) : null;
+            listener = (IntentEvent event) -> {
+                if (Objects.equals(event.subject().key(), key)) {
+                    if (event.type() == IntentEvent.Type.WITHDRAWN ||
+                            event.type() == IntentEvent.Type.FAILED) {
+                        withdrawLatch.countDown();
+                    } else if (purgeLatch != null && purgeAfterRemove &&
+                            event.type() == IntentEvent.Type.PURGED) {
+                        purgeLatch.countDown();
+                    }
+                }
+            };
+            intentService.addListener(listener);
+        } else {
+            purgeLatch = null;
+            withdrawLatch = null;
+        }
+
+        // request the withdraw
+        intentService.withdraw(intent);
+
+        if ((purgeAfterRemove || sync) && purgeLatch != null) {
+            try { // wait for withdraw event
+                withdrawLatch.await(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                print("Timed out waiting for intent {} withdraw", key);
+            }
+            if (purgeAfterRemove && CAN_PURGE.contains(intentService.getIntentState(key))) {
+                intentService.purge(intent);
+                if (sync) { // wait for purge event
+                    /* TODO
+                       Technically, the event comes before map.remove() is called.
+                       If we depend on sync and purge working together, we will
+                       need to address this.
+                    */
+                    try {
+                        purgeLatch.await(5, TimeUnit.SECONDS);
+                    } catch (InterruptedException e) {
+                        print("Timed out waiting for intent {} purge", key);
+                    }
+                }
+            }
+        }
+
+        if (listener != null) {
+            // clean up the listener
+            intentService.removeListener(listener);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkListCommand.java
new file mode 100644
index 0000000..df1172c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkListCommand.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Lists all virtual networks for the tenant ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnets",
+        description = "Lists all virtual networks.")
+public class VirtualNetworkListCommand extends AbstractShellCommand {
+
+    private static final String FMT_VIRTUAL_NETWORK =
+            "tenantId=%s, networkId=%s";
+
+    @Override
+    protected void doExecute() {
+
+        getSortedVirtualNetworks().forEach(this::printVirtualNetwork);
+    }
+
+    /**
+     * Returns the list of virtual networks sorted using the tenant identifier.
+     *
+     * @return sorted virtual network list
+     */
+    private List<VirtualNetwork> getSortedVirtualNetworks() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+        VirtualNetworkAdminService adminService = get(VirtualNetworkAdminService.class);
+
+        List<VirtualNetwork> virtualNetworks = new ArrayList<>();
+
+        Set<TenantId> tenantSet = adminService.getTenantIds();
+        tenantSet.forEach(tenantId -> virtualNetworks.addAll(service.getVirtualNetworks(tenantId)));
+
+        Collections.sort(virtualNetworks, Comparators.VIRTUAL_NETWORK_COMPARATOR);
+        return virtualNetworks;
+    }
+
+    /**
+     * Prints out each virtual network.
+     *
+     * @param virtualNetwork virtual network
+     */
+    private void printVirtualNetwork(VirtualNetwork virtualNetwork) {
+        print(FMT_VIRTUAL_NETWORK, virtualNetwork.tenantId(), virtualNetwork.id());
+    }
+}
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkPacketRequestCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkPacketRequestCommand.java
new file mode 100644
index 0000000..059829d
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkPacketRequestCommand.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.EthType;
+import org.onosproject.cli.net.EthTypeCompleter;
+import org.onosproject.cli.net.ExtHeader;
+import org.onosproject.cli.net.ExtHeaderCompleter;
+import org.onosproject.cli.net.Icmp6Code;
+import org.onosproject.cli.net.Icmp6CodeCompleter;
+import org.onosproject.cli.net.Icmp6Type;
+import org.onosproject.cli.net.Icmp6TypeCompleter;
+import org.onosproject.cli.net.IpProtocol;
+import org.onosproject.cli.net.IpProtocolCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketService;
+
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Tests virtual network packet requests.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-packet",
+        description = "Tests virtual network packet requests")
+public class VirtualNetworkPacketRequestCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "command",
+            description = "Command name (requestPackets|getRequests|cancelPackets)",
+            required = true, multiValued = false)
+    private String command = null;
+
+    @Argument(index = 1, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    private Long networkId = null;
+
+    @Option(name = "--deviceId", description = "Device ID",
+            required = false, multiValued = false)
+    private String deviceIdString = null;
+
+    // Traffic selector
+    @Option(name = "-s", aliases = "--ethSrc", description = "Source MAC Address",
+            required = false, multiValued = false)
+    private String srcMacString = null;
+
+    @Option(name = "-d", aliases = "--ethDst", description = "Destination MAC Address",
+            required = false, multiValued = false)
+    private String dstMacString = null;
+
+    @Option(name = "-t", aliases = "--ethType", description = "Ethernet Type",
+            required = false, multiValued = false)
+    @Completion(EthTypeCompleter.class)
+    private String ethTypeString = null;
+
+    @Option(name = "-v", aliases = "--vlan", description = "VLAN ID",
+            required = false, multiValued = false)
+    private String vlanString = null;
+
+    @Option(name = "--ipProto", description = "IP Protocol",
+            required = false, multiValued = false)
+    @Completion(IpProtocolCompleter.class)
+    private String ipProtoString = null;
+
+    @Option(name = "--ipSrc", description = "Source IP Prefix",
+            required = false, multiValued = false)
+    private String srcIpString = null;
+
+    @Option(name = "--ipDst", description = "Destination IP Prefix",
+            required = false, multiValued = false)
+    private String dstIpString = null;
+
+    @Option(name = "--fLabel", description = "IPv6 Flow Label",
+            required = false, multiValued = false)
+    private String fLabelString = null;
+
+    @Option(name = "--icmp6Type", description = "ICMPv6 Type",
+            required = false, multiValued = false)
+    @Completion(Icmp6TypeCompleter.class)
+    private String icmp6TypeString = null;
+
+    @Option(name = "--icmp6Code", description = "ICMPv6 Code",
+            required = false, multiValued = false)
+    @Completion(Icmp6CodeCompleter.class)
+    private String icmp6CodeString = null;
+
+    @Option(name = "--ndTarget", description = "IPv6 Neighbor Discovery Target Address",
+            required = false, multiValued = false)
+    private String ndTargetString = null;
+
+    @Option(name = "--ndSLL", description = "IPv6 Neighbor Discovery Source Link-Layer",
+            required = false, multiValued = false)
+    private String ndSllString = null;
+
+    @Option(name = "--ndTLL", description = "IPv6 Neighbor Discovery Target Link-Layer",
+            required = false, multiValued = false)
+    private String ndTllString = null;
+
+    @Option(name = "--tcpSrc", description = "Source TCP Port",
+            required = false, multiValued = false)
+    private String srcTcpString = null;
+
+    @Option(name = "--tcpDst", description = "Destination TCP Port",
+            required = false, multiValued = false)
+    private String dstTcpString = null;
+
+    @Option(name = "--extHdr", description = "IPv6 Extension Header Pseudo-field",
+            required = false, multiValued = true)
+    @Completion(ExtHeaderCompleter.class)
+    private List<String> extHdrStringList = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+        PacketService virtualPacketService = service.get(NetworkId.networkId(networkId), PacketService.class);
+
+        if (command == null) {
+            print("Command is not defined");
+            return;
+        }
+
+        if (command.equals("getRequests")) {
+            getRequests(virtualPacketService);
+            return;
+        }
+
+        TrafficSelector selector = buildTrafficSelector();
+        PacketPriority packetPriority = PacketPriority.CONTROL; //TODO allow user to specify
+        Optional<DeviceId> optionalDeviceId = null;
+        if (!isNullOrEmpty(deviceIdString)) {
+            optionalDeviceId = Optional.of(DeviceId.deviceId(deviceIdString));
+        }
+
+        if (command.equals("requestPackets")) {
+            if (optionalDeviceId != null) {
+                virtualPacketService.requestPackets(selector, packetPriority, appId(), optionalDeviceId);
+            } else {
+                virtualPacketService.requestPackets(selector, packetPriority, appId());
+            }
+            print("Virtual packet requested:\n%s", selector);
+            return;
+        }
+
+       if (command.equals("cancelPackets")) {
+            if (optionalDeviceId != null) {
+                virtualPacketService.cancelPackets(selector, packetPriority, appId(), optionalDeviceId);
+            } else {
+                virtualPacketService.cancelPackets(selector, packetPriority, appId());
+            }
+            print("Virtual packet cancelled:\n%s", selector);
+            return;
+        }
+
+        print("Unsupported command %s", command);
+    }
+
+    private void getRequests(PacketService packetService) {
+        List<PacketRequest> packetRequests = packetService.getRequests();
+        if (outputJson()) {
+            print("%s", json(packetRequests));
+        } else {
+            packetRequests.forEach(packetRequest -> print(packetRequest.toString()));
+        }
+    }
+
+    private JsonNode json(List<PacketRequest> packetRequests) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+        packetRequests.forEach(packetRequest ->
+                                       result.add(jsonForEntity(packetRequest, PacketRequest.class)));
+        return result;
+    }
+
+    /**
+     * Constructs a traffic selector based on the command line arguments
+     * presented to the command.
+     * @return traffic selector
+     */
+    private TrafficSelector buildTrafficSelector() {
+        IpPrefix srcIpPrefix = null;
+        IpPrefix dstIpPrefix = null;
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+
+        if (!isNullOrEmpty(srcIpString)) {
+            srcIpPrefix = IpPrefix.valueOf(srcIpString);
+            if (srcIpPrefix.isIp4()) {
+                selectorBuilder.matchIPSrc(srcIpPrefix);
+            } else {
+                selectorBuilder.matchIPv6Src(srcIpPrefix);
+            }
+        }
+
+        if (!isNullOrEmpty(dstIpString)) {
+            dstIpPrefix = IpPrefix.valueOf(dstIpString);
+            if (dstIpPrefix.isIp4()) {
+                selectorBuilder.matchIPDst(dstIpPrefix);
+            } else {
+                selectorBuilder.matchIPv6Dst(dstIpPrefix);
+            }
+        }
+
+        if ((srcIpPrefix != null) && (dstIpPrefix != null) &&
+            (srcIpPrefix.version() != dstIpPrefix.version())) {
+            // ERROR: IP src/dst version mismatch
+            throw new IllegalArgumentException(
+                        "IP source and destination version mismatch");
+        }
+
+        //
+        // Set the default EthType based on the IP version if the matching
+        // source or destination IP prefixes.
+        //
+        Short ethType = null;
+        if ((srcIpPrefix != null) && srcIpPrefix.isIp6()) {
+            ethType = EthType.IPV6.value();
+        }
+        if ((dstIpPrefix != null) && dstIpPrefix.isIp6()) {
+            ethType = EthType.IPV6.value();
+        }
+        if (!isNullOrEmpty(ethTypeString)) {
+            ethType = EthType.parseFromString(ethTypeString);
+        }
+        if (ethType != null) {
+            selectorBuilder.matchEthType(ethType);
+        }
+        if (!isNullOrEmpty(vlanString)) {
+            selectorBuilder.matchVlanId(VlanId.vlanId(Short.parseShort(vlanString)));
+        }
+        if (!isNullOrEmpty(srcMacString)) {
+            selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMacString));
+        }
+
+        if (!isNullOrEmpty(dstMacString)) {
+            selectorBuilder.matchEthDst(MacAddress.valueOf(dstMacString));
+        }
+
+        if (!isNullOrEmpty(ipProtoString)) {
+            short ipProtoShort = IpProtocol.parseFromString(ipProtoString);
+            selectorBuilder.matchIPProtocol((byte) ipProtoShort);
+        }
+
+        if (!isNullOrEmpty(fLabelString)) {
+            selectorBuilder.matchIPv6FlowLabel(Integer.parseInt(fLabelString));
+        }
+
+        if (!isNullOrEmpty(icmp6TypeString)) {
+            byte icmp6Type = Icmp6Type.parseFromString(icmp6TypeString);
+            selectorBuilder.matchIcmpv6Type(icmp6Type);
+        }
+
+        if (!isNullOrEmpty(icmp6CodeString)) {
+            byte icmp6Code = Icmp6Code.parseFromString(icmp6CodeString);
+            selectorBuilder.matchIcmpv6Code(icmp6Code);
+        }
+
+        if (!isNullOrEmpty(ndTargetString)) {
+            selectorBuilder.matchIPv6NDTargetAddress(Ip6Address.valueOf(ndTargetString));
+        }
+
+        if (!isNullOrEmpty(ndSllString)) {
+            selectorBuilder.matchIPv6NDSourceLinkLayerAddress(MacAddress.valueOf(ndSllString));
+        }
+
+        if (!isNullOrEmpty(ndTllString)) {
+            selectorBuilder.matchIPv6NDTargetLinkLayerAddress(MacAddress.valueOf(ndTllString));
+        }
+
+        if (!isNullOrEmpty(srcTcpString)) {
+            selectorBuilder.matchTcpSrc(TpPort.tpPort(Integer.parseInt(srcTcpString)));
+        }
+
+        if (!isNullOrEmpty(dstTcpString)) {
+            selectorBuilder.matchTcpDst(TpPort.tpPort(Integer.parseInt(dstTcpString)));
+        }
+
+        if (extHdrStringList != null) {
+            short extHdr = 0;
+            for (String extHdrString : extHdrStringList) {
+                extHdr = (short) (extHdr | ExtHeader.parseFromString(extHdrString));
+            }
+            selectorBuilder.matchIPv6ExthdrFlags(extHdr);
+        }
+
+        return selectorBuilder.build();
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkRemoveCommand.java
new file mode 100644
index 0000000..2b28044
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualNetworkRemoveCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+
+/**
+ * Removes a virtual network.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove",
+        description = "Removes a virtual network.")
+public class VirtualNetworkRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "id", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long id;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.removeVirtualNetwork(NetworkId.networkId(id));
+        print("Virtual network successfully removed.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortBindCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortBindCommand.java
new file mode 100644
index 0000000..7013a01
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortBindCommand.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.DeviceIdCompleter;
+import org.onosproject.cli.net.PortNumberCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+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.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Binds an existing virtual port with a physical port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-bind-port",
+        description = "Binds an existing virtual port with a physical port.")
+public class VirtualPortBindCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String deviceId = null;
+
+    @Argument(index = 2, name = "portNum", description = "Virtual device port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer portNum = null;
+
+    @Argument(index = 3, name = "physDeviceId", description = "Physical Device ID",
+            required = true, multiValued = false)
+    @Completion(DeviceIdCompleter.class)
+    String physDeviceId = null;
+
+    @Argument(index = 4, name = "physPortNum", description = "Physical device port number",
+            required = true, multiValued = false)
+    @Completion(PortNumberCompleter.class)
+    Integer physPortNum = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        DeviceService deviceService = get(DeviceService.class);
+
+        VirtualPort vPort = getVirtualPort(PortNumber.portNumber(portNum));
+        checkNotNull(vPort, "The virtual Port does not exist");
+
+        ConnectPoint realizedBy = new ConnectPoint(DeviceId.deviceId(physDeviceId),
+                                      PortNumber.portNumber(physPortNum));
+        service.bindVirtualPort(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId),
+                                  PortNumber.portNumber(portNum), realizedBy);
+        print("Virtual port is successfully bound.");
+    }
+
+    /**
+     * Returns the virtual port matching the device and port identifier.
+     *
+     * @param aPortNumber port identifier
+     * @return matching virtual port, or null.
+     */
+    private VirtualPort getVirtualPort(PortNumber aPortNumber) {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+        Set<VirtualPort> ports = service.getVirtualPorts(NetworkId.networkId(networkId),
+                                                    DeviceId.deviceId(deviceId));
+        return ports.stream().filter(p -> p.number().equals(aPortNumber))
+                .findFirst().get();
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCompleter.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCompleter.java
new file mode 100644
index 0000000..141c964
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCompleter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import static org.onlab.osgi.DefaultServiceDirectory.getService;
+
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractChoicesCompleter;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.DeviceId;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+/**
+ * Virtual port completer.
+ *
+ * Assumes the first argument which can be parsed to a number is network id
+ * and the argument right before the one being completed is device id
+ */
+@Service
+public class VirtualPortCompleter extends AbstractChoicesCompleter {
+    @Override
+    protected List<String> choices() {
+        //parse argument list for network id
+        String[] argsArray = commandLine.getArguments();
+        for (String str : argsArray) {
+            if (str.matches("[0-9]+")) {
+                long networkId = Long.valueOf(str);
+                String deviceId = argsArray[argsArray.length - 1];
+                return getSortedVirtualPorts(networkId, deviceId).stream()
+                        .map(virtualPort -> virtualPort.number().toString())
+                        .collect(Collectors.toList());
+            }
+        }
+
+        return Collections.singletonList("Missing network id");
+    }
+
+    /**
+     * Returns the list of virtual ports sorted using the port number.
+     *
+     * @param networkId network id.
+     * @param deviceId device id
+     * @return sorted virtual port list
+     */
+    private List<VirtualPort> getSortedVirtualPorts(long networkId, String deviceId) {
+        VirtualNetworkService service = getService(VirtualNetworkService.class);
+
+        List<VirtualPort> virtualPorts = new ArrayList<>();
+        virtualPorts.addAll(service.getVirtualPorts(NetworkId.networkId(networkId),
+                DeviceId.deviceId(deviceId)));
+        Collections.sort(virtualPorts, Comparators.VIRTUAL_PORT_COMPARATOR);
+        return virtualPorts;
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCreateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCreateCommand.java
new file mode 100644
index 0000000..a466a64
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortCreateCommand.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.DeviceIdCompleter;
+import org.onosproject.cli.net.PortNumberCompleter;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Creates a new virtual port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-create-port",
+        description = "Creates a new virtual port in a network.")
+public class VirtualPortCreateCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String deviceId = null;
+
+    @Argument(index = 2, name = "portNum", description = "Virtual device port number",
+            required = true, multiValued = false)
+    Integer portNum = null;
+
+    @Argument(index = 3, name = "physDeviceId", description = "Physical Device ID",
+            required = false, multiValued = false)
+    @Completion(DeviceIdCompleter.class)
+    String physDeviceId = null;
+
+    @Argument(index = 4, name = "physPortNum", description = "Physical device port number",
+            required = false, multiValued = false)
+    @Completion(PortNumberCompleter.class)
+    Integer physPortNum = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        DeviceService deviceService = get(DeviceService.class);
+
+        VirtualDevice virtualDevice = getVirtualDevice(DeviceId.deviceId(deviceId));
+        checkNotNull(virtualDevice, "The virtual device does not exist.");
+
+        ConnectPoint realizedBy = null;
+        if (physDeviceId != null && physPortNum != null) {
+            checkNotNull(physPortNum, "The physical port does not specified.");
+            realizedBy = new ConnectPoint(DeviceId.deviceId(physDeviceId),
+                                               PortNumber.portNumber(physPortNum));
+            checkNotNull(realizedBy, "The physical port does not exist.");
+        }
+
+        service.createVirtualPort(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId),
+                                  PortNumber.portNumber(portNum), realizedBy);
+        print("Virtual port successfully created.");
+    }
+
+    /**
+     * Returns the virtual device matching the device identifier.
+     *
+     * @param aDeviceId device identifier
+     * @return matching virtual device, or null.
+     */
+    private VirtualDevice getVirtualDevice(DeviceId aDeviceId) {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+
+        Set<VirtualDevice> virtualDevices = service.getVirtualDevices(NetworkId.networkId(networkId));
+
+        for (VirtualDevice virtualDevice : virtualDevices) {
+            if (virtualDevice.id().equals(aDeviceId)) {
+                return virtualDevice;
+            }
+        }
+        return null;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortListCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortListCommand.java
new file mode 100644
index 0000000..f7a6c08
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortListCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.Comparators;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.DeviceId;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all virtual ports for the network ID.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-ports",
+        description = "Lists all virtual ports in a virtual network.")
+public class VirtualPortListCommand extends AbstractShellCommand {
+
+    private static final String FMT_VIRTUAL_PORT =
+            "virtual portNumber=%s, physical deviceId=%s, portNumber=%s, isEnabled=%s";
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String deviceId = null;
+
+    @Override
+    protected void doExecute() {
+
+        getSortedVirtualPorts().forEach(this::printVirtualPort);
+    }
+
+    /**
+     * Returns the list of virtual ports sorted using the network identifier.
+     *
+     * @return sorted virtual port list
+     */
+    private List<VirtualPort> getSortedVirtualPorts() {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+
+        List<VirtualPort> virtualPorts = new ArrayList<>();
+        virtualPorts.addAll(service.getVirtualPorts(NetworkId.networkId(networkId),
+                                                    DeviceId.deviceId(deviceId)));
+        Collections.sort(virtualPorts, Comparators.VIRTUAL_PORT_COMPARATOR);
+        return virtualPorts;
+    }
+
+    /**
+     * Prints out each virtual port.
+     *
+     * @param virtualPort virtual port
+     */
+    private void printVirtualPort(VirtualPort virtualPort) {
+        if (virtualPort.realizedBy() == null) {
+            print(FMT_VIRTUAL_PORT, virtualPort.number(), "None", "None", virtualPort.isEnabled());
+        } else {
+            print(FMT_VIRTUAL_PORT, virtualPort.number(),
+                  virtualPort.realizedBy().deviceId(),
+                  virtualPort.realizedBy().port(),
+                  virtualPort.isEnabled());
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortRemoveCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortRemoveCommand.java
new file mode 100644
index 0000000..6c79276
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortRemoveCommand.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Removes a virtual port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-remove-port",
+        description = "Removes a virtual port.")
+public class VirtualPortRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String deviceId = null;
+
+    @Argument(index = 2, name = "portNum", description = "Device port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer portNum = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+        service.removeVirtualPort(NetworkId.networkId(networkId), DeviceId.deviceId(deviceId),
+                                  PortNumber.portNumber(portNum));
+        print("Virtual port successfully removed.");
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortStateCommand.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortStateCommand.java
new file mode 100644
index 0000000..56af2ba
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/VirtualPortStateCommand.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.virtual.NetworkId;
+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.DeviceId;
+import org.onosproject.net.PortNumber;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Administratively enables or disables state of an existing virtual port.
+ */
+@Service
+@Command(scope = "onos", name = "vnet-port-state",
+        description = "Administratively enables or disables state of an existing virtual port.")
+public class VirtualPortStateCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "networkId", description = "Network ID",
+            required = true, multiValued = false)
+    @Completion(VirtualNetworkCompleter.class)
+    Long networkId = null;
+
+    @Argument(index = 1, name = "deviceId", description = "Virtual Device ID",
+            required = true, multiValued = false)
+    @Completion(VirtualDeviceCompleter.class)
+    String deviceId = null;
+
+    @Argument(index = 2, name = "portNum", description = "Virtual device port number",
+            required = true, multiValued = false)
+    @Completion(VirtualPortCompleter.class)
+    Integer portNum = null;
+
+    @Argument(index = 3, name = "portState",
+            description = "Desired State. Either \"enable\" or \"disable\".",
+            required = true, multiValued = false)
+    String portState = null;
+
+    @Override
+    protected void doExecute() {
+        VirtualNetworkAdminService service = get(VirtualNetworkAdminService.class);
+
+        VirtualPort vPort = getVirtualPort(PortNumber.portNumber(portNum));
+        checkNotNull(vPort, "The virtual Port does not exist");
+
+        boolean isEnabled;
+        if ("enable".equals(portState)) {
+            isEnabled = true;
+        } else if ("disable".equals(portState)) {
+            isEnabled = false;
+        } else {
+            print("State must be enable or disable");
+            return;
+        }
+
+        service.updatePortState(NetworkId.networkId(networkId),
+                                DeviceId.deviceId(deviceId), vPort.number(), isEnabled);
+        print("Virtual port state updated.");
+    }
+
+    /**
+     * Returns the virtual port matching the device and port identifier.
+     *
+     * @param aPortNumber port identifier
+     * @return matching virtual port, or null.
+     */
+    private VirtualPort getVirtualPort(PortNumber aPortNumber) {
+        VirtualNetworkService service = get(VirtualNetworkService.class);
+        Set<VirtualPort> ports = service.getVirtualPorts(NetworkId.networkId(networkId),
+                                                    DeviceId.deviceId(deviceId));
+        return ports.stream().filter(p -> p.number().equals(aPortNumber))
+                .findFirst().get();
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/package-info.java
new file mode 100644
index 0000000..049ee4f
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * CLI commands for querying and administering virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.cli;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
new file mode 100644
index 0000000..21ba3b9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceEvent.Type;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Device service implementation built on the virtual network service.
+ */
+public class VirtualNetworkDeviceManager
+        extends AbstractVirtualListenerManager<DeviceEvent, DeviceListener>
+        implements DeviceService {
+
+    private static final String TYPE_NULL = "Type cannot be null";
+    private static final String DEVICE_NULL = "Device cannot be null";
+    private static final String PORT_NUMBER_NULL = "PortNumber cannot be null";
+    private VirtualNetworkListener virtualNetworkListener = new InternalVirtualNetworkListener();
+
+    /**
+     * Creates a new VirtualNetworkDeviceService object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+    public VirtualNetworkDeviceManager(VirtualNetworkService virtualNetworkManager,
+                                       NetworkId networkId) {
+        super(virtualNetworkManager, networkId, DeviceEvent.class);
+        manager.addListener(virtualNetworkListener);
+    }
+
+    @Override
+    public int getDeviceCount() {
+        return manager.getVirtualDevices(this.networkId).size();
+    }
+
+    @Override
+    public Iterable<Device> getDevices() {
+        return manager.getVirtualDevices(
+                this.networkId).stream().collect(Collectors.toSet());
+    }
+
+    @Override
+    public Iterable<Device> getDevices(Device.Type type) {
+        checkNotNull(type, TYPE_NULL);
+        return manager.getVirtualDevices(this.networkId)
+                .stream()
+                .filter(device -> type.equals(device.type()))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Iterable<Device> getAvailableDevices() {
+        return getDevices();
+    }
+
+    @Override
+    public Iterable<Device> getAvailableDevices(Device.Type type) {
+        return getDevices(type);
+    }
+
+    @Override
+    public Device getDevice(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        Optional<VirtualDevice> foundDevice =
+                manager.getVirtualDevices(this.networkId)
+                .stream()
+                .filter(device -> deviceId.equals(device.id()))
+                .findFirst();
+        if (foundDevice.isPresent()) {
+            return foundDevice.get();
+        }
+        return null;
+    }
+
+    @Override
+    public MastershipRole getRole(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        // TODO hard coded to master for now.
+        return MastershipRole.MASTER;
+    }
+
+    @Override
+    public List<Port> getPorts(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        return manager.getVirtualPorts(this.networkId, deviceId)
+                .stream()
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<PortStatistics> getPortStatistics(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        // TODO not supported at the moment.
+        return ImmutableList.of();
+    }
+
+    @Override
+    public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        // TODO not supported at the moment.
+        return ImmutableList.of();
+    }
+
+    @Override
+    public PortStatistics getStatisticsForPort(DeviceId deviceId,
+                                               PortNumber portNumber) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        checkNotNull(deviceId, PORT_NUMBER_NULL);
+        // TODO not supported at the moment.
+        return null;
+    }
+
+    @Override
+    public PortStatistics getDeltaStatisticsForPort(DeviceId deviceId,
+                                                    PortNumber portNumber) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        checkNotNull(deviceId, PORT_NUMBER_NULL);
+        // TODO not supported at the moment.
+        return null;
+    }
+
+    @Override
+    public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+        checkNotNull(deviceId, DEVICE_NULL);
+
+        Optional<VirtualPort> foundPort =
+                manager.getVirtualPorts(this.networkId, deviceId)
+                .stream()
+                .filter(port -> port.number().equals(portNumber))
+                .findFirst();
+        if (foundPort.isPresent()) {
+            return foundPort.get();
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isAvailable(DeviceId deviceId) {
+        return getDevice(deviceId) != null;
+    }
+
+    @Override
+    public String localStatus(DeviceId deviceId) {
+        // TODO not supported at this time
+        return null;
+    }
+
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        // TODO not supported at this time
+        return 0;
+    }
+
+    /**
+     * Translates VirtualNetworkEvent to DeviceEvent.
+     */
+    private class InternalVirtualNetworkListener implements VirtualNetworkListener {
+        @Override
+        public boolean isRelevant(VirtualNetworkEvent event) {
+            return networkId().equals(event.subject());
+        }
+
+        @Override
+        public void event(VirtualNetworkEvent event) {
+            switch (event.type()) {
+                case VIRTUAL_DEVICE_ADDED:
+                    post(new DeviceEvent(Type.DEVICE_ADDED, event.virtualDevice()));
+                    break;
+                case VIRTUAL_DEVICE_UPDATED:
+                    post(new DeviceEvent(Type.DEVICE_UPDATED, event.virtualDevice()));
+                    break;
+                case VIRTUAL_DEVICE_REMOVED:
+                    post(new DeviceEvent(Type.DEVICE_REMOVED, event.virtualDevice()));
+                    break;
+                case VIRTUAL_PORT_ADDED:
+                    post(new DeviceEvent(Type.PORT_ADDED, event.virtualDevice(), event.virtualPort()));
+                    break;
+                case VIRTUAL_PORT_UPDATED:
+                    post(new DeviceEvent(Type.PORT_UPDATED, event.virtualDevice(), event.virtualPort()));
+                    break;
+                case VIRTUAL_PORT_REMOVED:
+                    post(new DeviceEvent(Type.PORT_REMOVED, event.virtualDevice(), event.virtualPort()));
+                    break;
+                case NETWORK_UPDATED:
+                case NETWORK_REMOVED:
+                case NETWORK_ADDED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManager.java
new file mode 100644
index 0000000..c4e2031
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManager.java
@@ -0,0 +1,716 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalCause;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.incubator.net.virtual.AbstractVnetService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.behaviour.PipelinerContext;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.flowobjective.ObjectiveEvent;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupKey;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of the flow objective programming service for virtual networks.
+ */
+// NOTE: This manager is designed to provide flow objective programming service
+// for virtual networks. Actually, virtual networks don't need to consider
+// the different implementation of data-path pipeline. But, the interfaces
+// and usages of flow objective service are still valuable for virtual network.
+// This manager is working as an interpreter from FlowObjective to FlowRules
+// to provide symmetric interfaces with ONOS core services.
+// The behaviours are based on DefaultSingleTablePipeline.
+
+public class VirtualNetworkFlowObjectiveManager extends AbstractVnetService
+        implements FlowObjectiveService {
+
+    public static final int INSTALL_RETRY_ATTEMPTS = 5;
+    public static final long INSTALL_RETRY_INTERVAL = 1000; // ms
+
+    private final Logger log = getLogger(getClass());
+
+    protected DeviceService deviceService;
+
+    // Note: The following dependencies are added on behalf of the pipeline
+    // driver behaviours to assure these services are available for their
+    // initialization.
+    protected FlowRuleService flowRuleService;
+
+    protected VirtualNetworkFlowObjectiveStore virtualFlowObjectiveStore;
+    protected FlowObjectiveStore flowObjectiveStore;
+    private final FlowObjectiveStoreDelegate delegate;
+
+    private final PipelinerContext context = new InnerPipelineContext();
+
+    private final Map<DeviceId, Pipeliner> pipeliners = Maps.newConcurrentMap();
+
+    // local stores for queuing fwd and next objectives that are waiting for an
+    // associated next objective execution to complete. The signal for completed
+    // execution comes from a pipeline driver, in this or another controller
+    // instance, via the DistributedFlowObjectiveStore.
+    private final Map<Integer, Set<PendingFlowObjective>> pendingForwards =
+            Maps.newConcurrentMap();
+    private final Map<Integer, Set<PendingFlowObjective>> pendingNexts =
+            Maps.newConcurrentMap();
+
+    // local store to track which nextObjectives were sent to which device
+    // for debugging purposes
+    private Map<Integer, DeviceId> nextToDevice = Maps.newConcurrentMap();
+
+    private ExecutorService executorService;
+
+    public VirtualNetworkFlowObjectiveManager(VirtualNetworkService manager,
+                                              NetworkId networkId) {
+        super(manager, networkId);
+
+        deviceService = manager.get(networkId(), DeviceService.class);
+        flowRuleService = manager.get(networkId(), FlowRuleService.class);
+
+        executorService = newFixedThreadPool(4, groupedThreads("onos/virtual/objective-installer", "%d", log));
+
+        virtualFlowObjectiveStore =
+                serviceDirectory.get(VirtualNetworkFlowObjectiveStore.class);
+        delegate = new InternalStoreDelegate();
+        virtualFlowObjectiveStore.setDelegate(networkId(), delegate);
+        flowObjectiveStore = new StoreConvertor();
+    }
+
+    @Override
+    public void filter(DeviceId deviceId, FilteringObjective filteringObjective) {
+        executorService.execute(new ObjectiveInstaller(deviceId, filteringObjective));
+    }
+
+    @Override
+    public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) {
+        if (forwardingObjective.nextId() == null ||
+                forwardingObjective.op() == Objective.Operation.REMOVE ||
+                flowObjectiveStore.getNextGroup(forwardingObjective.nextId()) != null ||
+                !queueFwdObjective(deviceId, forwardingObjective)) {
+            // fast path
+            executorService.execute(new ObjectiveInstaller(deviceId, forwardingObjective));
+        }
+    }
+
+    @Override
+    public void next(DeviceId deviceId, NextObjective nextObjective) {
+        nextToDevice.put(nextObjective.id(), deviceId);
+        if (nextObjective.op() == Objective.Operation.ADD ||
+                flowObjectiveStore.getNextGroup(nextObjective.id()) != null ||
+                !queueNextObjective(deviceId, nextObjective)) {
+            // either group exists or we are trying to create it - let it through
+            executorService.execute(new ObjectiveInstaller(deviceId, nextObjective));
+        }
+    }
+
+    @Override
+    public int allocateNextId() {
+        return flowObjectiveStore.allocateNextId();
+    }
+
+    @Override
+    public void initPolicy(String policy) {
+
+    }
+
+    @Override
+    public List<String> getNextMappings() {
+        List<String> mappings = new ArrayList<>();
+        Map<Integer, NextGroup> allnexts = flowObjectiveStore.getAllGroups();
+        // XXX if the NextGroup after de-serialization actually stored info of the deviceId
+        // then info on any nextObj could be retrieved from one controller instance.
+        // Right now the drivers on one instance can only fetch for next-ids that came
+        // to them.
+        // Also, we still need to send the right next-id to the right driver as potentially
+        // there can be different drivers for different devices. But on that account,
+        // no instance should be decoding for another instance's nextIds.
+
+        for (Map.Entry<Integer, NextGroup> e : allnexts.entrySet()) {
+            // get the device this next Objective was sent to
+            DeviceId deviceId = nextToDevice.get(e.getKey());
+            mappings.add("NextId " + e.getKey() + ": " +
+                                 ((deviceId != null) ? deviceId : "nextId not in this onos instance"));
+            if (deviceId != null) {
+                // this instance of the controller sent the nextObj to a driver
+                Pipeliner pipeliner = getDevicePipeliner(deviceId);
+                List<String> nextMappings = pipeliner.getNextMappings(e.getValue());
+                if (nextMappings != null) {
+                    mappings.addAll(nextMappings);
+                }
+            }
+        }
+        return mappings;
+    }
+
+    @Override
+    public List<String> getPendingFlowObjectives() {
+        List<String> pendingFlowObjectives = new ArrayList<>();
+
+        for (Integer nextId : pendingForwards.keySet()) {
+            Set<PendingFlowObjective> pfwd = pendingForwards.get(nextId);
+            StringBuilder pend = new StringBuilder();
+            pend.append("NextId: ")
+                    .append(nextId);
+            for (PendingFlowObjective pf : pfwd) {
+                pend.append("\n    FwdId: ")
+                        .append(String.format("%11s", pf.flowObjective().id()))
+                        .append(", DeviceId: ")
+                        .append(pf.deviceId())
+                        .append(", Selector: ")
+                        .append(((ForwardingObjective) pf.flowObjective())
+                                        .selector().criteria());
+            }
+            pendingFlowObjectives.add(pend.toString());
+        }
+
+        for (Integer nextId : pendingNexts.keySet()) {
+            Set<PendingFlowObjective> pnext = pendingNexts.get(nextId);
+            StringBuilder pend = new StringBuilder();
+            pend.append("NextId: ")
+                    .append(nextId);
+            for (PendingFlowObjective pn : pnext) {
+                pend.append("\n    NextOp: ")
+                        .append(pn.flowObjective().op())
+                        .append(", DeviceId: ")
+                        .append(pn.deviceId())
+                        .append(", Treatments: ")
+                        .append(((NextObjective) pn.flowObjective())
+                                        .next());
+            }
+            pendingFlowObjectives.add(pend.toString());
+        }
+
+        return pendingFlowObjectives;
+    }
+
+    private boolean queueFwdObjective(DeviceId deviceId, ForwardingObjective fwd) {
+        boolean queued = false;
+        synchronized (pendingForwards) {
+            // double check the flow objective store, because this block could run
+            // after a notification arrives
+            if (flowObjectiveStore.getNextGroup(fwd.nextId()) == null) {
+                pendingForwards.compute(fwd.nextId(), (id, pending) -> {
+                    PendingFlowObjective pendfo = new PendingFlowObjective(deviceId, fwd);
+                    if (pending == null) {
+                        return Sets.newHashSet(pendfo);
+                    } else {
+                        pending.add(pendfo);
+                        return pending;
+                    }
+                });
+                queued = true;
+            }
+        }
+        if (queued) {
+            log.debug("Queued forwarding objective {} for nextId {} meant for device {}",
+                      fwd.id(), fwd.nextId(), deviceId);
+        }
+        return queued;
+    }
+
+    private boolean queueNextObjective(DeviceId deviceId, NextObjective next) {
+
+        // we need to hold off on other operations till we get notified that the
+        // initial group creation has succeeded
+        boolean queued = false;
+        synchronized (pendingNexts) {
+            // double check the flow objective store, because this block could run
+            // after a notification arrives
+            if (flowObjectiveStore.getNextGroup(next.id()) == null) {
+                pendingNexts.compute(next.id(), (id, pending) -> {
+                    PendingFlowObjective pendfo = new PendingFlowObjective(deviceId, next);
+                    if (pending == null) {
+                        return Sets.newHashSet(pendfo);
+                    } else {
+                        pending.add(pendfo);
+                        return pending;
+                    }
+                });
+                queued = true;
+            }
+        }
+        if (queued) {
+            log.debug("Queued next objective {} with operation {} meant for device {}",
+                      next.id(), next.op(), deviceId);
+        }
+        return queued;
+    }
+
+    /**
+     * Task that passes the flow objective down to the driver. The task will
+     * make a few attempts to find the appropriate driver, then eventually give
+     * up and report an error if no suitable driver could be found.
+     */
+    private class ObjectiveInstaller implements Runnable {
+        private final DeviceId deviceId;
+        private final Objective objective;
+
+        private final int numAttempts;
+
+        public ObjectiveInstaller(DeviceId deviceId, Objective objective) {
+            this(deviceId, objective, 1);
+        }
+
+        public ObjectiveInstaller(DeviceId deviceId, Objective objective, int attemps) {
+            this.deviceId = checkNotNull(deviceId);
+            this.objective = checkNotNull(objective);
+            this.numAttempts = attemps;
+        }
+
+        @Override
+        public void run() {
+            try {
+                Pipeliner pipeliner = getDevicePipeliner(deviceId);
+
+                if (pipeliner != null) {
+                    if (objective instanceof NextObjective) {
+                        nextToDevice.put(objective.id(), deviceId);
+                        pipeliner.next((NextObjective) objective);
+                    } else if (objective instanceof ForwardingObjective) {
+                        pipeliner.forward((ForwardingObjective) objective);
+                    } else {
+                        pipeliner.filter((FilteringObjective) objective);
+                    }
+                    //Attempts to check if pipeliner is null for retry attempts
+                } else if (numAttempts < INSTALL_RETRY_ATTEMPTS) {
+                    Thread.sleep(INSTALL_RETRY_INTERVAL);
+                    executorService.execute(new ObjectiveInstaller(deviceId, objective, numAttempts + 1));
+                } else {
+                    // Otherwise we've tried a few times and failed, report an
+                    // error back to the user.
+                    objective.context().ifPresent(
+                            c -> c.onError(objective, ObjectiveError.NOPIPELINER));
+                }
+                //Exception thrown
+            } catch (Exception e) {
+                log.warn("Exception while installing flow objective", e);
+            }
+        }
+    }
+
+    private class InternalStoreDelegate implements FlowObjectiveStoreDelegate {
+        @Override
+        public void notify(ObjectiveEvent event) {
+            if (event.type() == ObjectiveEvent.Type.ADD) {
+                log.debug("Received notification of obj event {}", event);
+                Set<PendingFlowObjective> pending;
+
+                // first send all pending flows
+                synchronized (pendingForwards) {
+                    // needs to be synchronized for queueObjective lookup
+                    pending = pendingForwards.remove(event.subject());
+                }
+                if (pending == null) {
+                    log.debug("No forwarding objectives pending for this "
+                                      + "obj event {}", event);
+                } else {
+                    log.debug("Processing {} pending forwarding objectives for nextId {}",
+                              pending.size(), event.subject());
+                    pending.forEach(p -> getDevicePipeliner(p.deviceId())
+                            .forward((ForwardingObjective) p.flowObjective()));
+                }
+
+                // now check for pending next-objectives
+                synchronized (pendingNexts) {
+                    // needs to be synchronized for queueObjective lookup
+                    pending = pendingNexts.remove(event.subject());
+                }
+                if (pending == null) {
+                    log.debug("No next objectives pending for this "
+                                      + "obj event {}", event);
+                } else {
+                    log.debug("Processing {} pending next objectives for nextId {}",
+                              pending.size(), event.subject());
+                    pending.forEach(p -> getDevicePipeliner(p.deviceId())
+                            .next((NextObjective) p.flowObjective()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Retrieves (if it exists) the device pipeline behaviour from the cache.
+     * Otherwise it warms the caches and triggers the init method of the Pipeline.
+     * For virtual network, it returns OVS pipeliner.
+     *
+     * @param deviceId the id of the device associated to the pipeline
+     * @return the implementation of the Pipeliner behaviour
+     */
+    private Pipeliner getDevicePipeliner(DeviceId deviceId) {
+        return pipeliners.computeIfAbsent(deviceId, this::initPipelineHandler);
+    }
+
+    /**
+     * Creates and initialize {@link Pipeliner}.
+     * <p>
+     * Note: Expected to be called under per-Device lock.
+     *      e.g., {@code pipeliners}' Map#compute family methods
+     *
+     * @param deviceId Device to initialize pipeliner
+     * @return {@link Pipeliner} instance or null
+     */
+    private Pipeliner initPipelineHandler(DeviceId deviceId) {
+        //FIXME: do we need a standard pipeline for virtual device?
+        Pipeliner pipeliner = new DefaultVirtualDevicePipeline();
+        pipeliner.init(deviceId, context);
+        return pipeliner;
+    }
+
+    // Processing context for initializing pipeline driver behaviours.
+    private class InnerPipelineContext implements PipelinerContext {
+        @Override
+        public ServiceDirectory directory() {
+            return serviceDirectory;
+        }
+
+        @Override
+        public FlowObjectiveStore store() {
+            return flowObjectiveStore;
+        }
+    }
+
+    /**
+     * Data class used to hold a pending flow objective that could not
+     * be processed because the associated next object was not present.
+     * Note that this pending flow objective could be a forwarding objective
+     * waiting for a next objective to complete execution. Or it could a
+     * next objective (with a different operation - remove, addToExisting, or
+     * removeFromExisting) waiting for a next objective with the same id to
+     * complete execution.
+     */
+    private class PendingFlowObjective {
+        private final DeviceId deviceId;
+        private final Objective flowObj;
+
+        public PendingFlowObjective(DeviceId deviceId, Objective flowObj) {
+            this.deviceId = deviceId;
+            this.flowObj = flowObj;
+        }
+
+        public DeviceId deviceId() {
+            return deviceId;
+        }
+
+        public Objective flowObjective() {
+            return flowObj;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(deviceId, flowObj);
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof PendingFlowObjective)) {
+                return false;
+            }
+            final PendingFlowObjective other = (PendingFlowObjective) obj;
+            if (this.deviceId.equals(other.deviceId) &&
+                    this.flowObj.equals(other.flowObj)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * This class is a wrapping class from VirtualNetworkFlowObjectiveStore
+     * to FlowObjectiveStore for PipelinerContext.
+     */
+    private class StoreConvertor implements FlowObjectiveStore {
+
+        @Override
+        public void setDelegate(FlowObjectiveStoreDelegate delegate) {
+            virtualFlowObjectiveStore.setDelegate(networkId(), delegate);
+        }
+
+        @Override
+        public void unsetDelegate(FlowObjectiveStoreDelegate delegate) {
+            virtualFlowObjectiveStore.unsetDelegate(networkId(), delegate);
+        }
+
+        @Override
+        public boolean hasDelegate() {
+            return virtualFlowObjectiveStore.hasDelegate(networkId());
+        }
+
+        @Override
+        public void putNextGroup(Integer nextId, NextGroup group) {
+            virtualFlowObjectiveStore.putNextGroup(networkId(), nextId, group);
+        }
+
+        @Override
+        public NextGroup getNextGroup(Integer nextId) {
+            return virtualFlowObjectiveStore.getNextGroup(networkId(), nextId);
+        }
+
+        @Override
+        public NextGroup removeNextGroup(Integer nextId) {
+            return virtualFlowObjectiveStore.removeNextGroup(networkId(), nextId);
+        }
+
+        @Override
+        public Map<Integer, NextGroup> getAllGroups() {
+            return virtualFlowObjectiveStore.getAllGroups(networkId());
+        }
+
+        @Override
+        public int allocateNextId() {
+            return virtualFlowObjectiveStore.allocateNextId(networkId());
+        }
+    }
+
+    /**
+     * Simple single table pipeline abstraction for virtual networks.
+     */
+    private class DefaultVirtualDevicePipeline
+            extends AbstractHandlerBehaviour implements Pipeliner {
+
+        private final Logger log = getLogger(getClass());
+
+        private DeviceId deviceId;
+
+        private Cache<Integer, NextObjective> pendingNext;
+
+        private KryoNamespace appKryo = new KryoNamespace.Builder()
+                .register(GroupKey.class)
+                .register(DefaultGroupKey.class)
+                .register(SingleGroup.class)
+                .register(byte[].class)
+                .build("DefaultVirtualDevicePipeline");
+
+        @Override
+        public void init(DeviceId deviceId, PipelinerContext context) {
+            this.deviceId = deviceId;
+
+            pendingNext = CacheBuilder.newBuilder()
+                    .expireAfterWrite(20, TimeUnit.SECONDS)
+                    .removalListener((RemovalNotification<Integer, NextObjective> notification) -> {
+                        if (notification.getCause() == RemovalCause.EXPIRED) {
+                            notification.getValue().context()
+                                    .ifPresent(c -> c.onError(notification.getValue(),
+                                                              ObjectiveError.FLOWINSTALLATIONFAILED));
+                        }
+                    }).build();
+        }
+
+        @Override
+        public void filter(FilteringObjective filter) {
+
+            TrafficTreatment.Builder actions;
+            switch (filter.type()) {
+                case PERMIT:
+                    actions = (filter.meta() == null) ?
+                            DefaultTrafficTreatment.builder().punt() :
+                            DefaultTrafficTreatment.builder(filter.meta());
+                    break;
+                case DENY:
+                    actions = (filter.meta() == null) ?
+                            DefaultTrafficTreatment.builder() :
+                            DefaultTrafficTreatment.builder(filter.meta());
+                    actions.drop();
+                    break;
+                default:
+                    log.warn("Unknown filter type: {}", filter.type());
+                    actions = DefaultTrafficTreatment.builder().drop();
+            }
+
+            TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+
+            filter.conditions().forEach(selector::add);
+
+            if (filter.key() != null) {
+                selector.add(filter.key());
+            }
+
+            FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                    .forDevice(deviceId)
+                    .withSelector(selector.build())
+                    .withTreatment(actions.build())
+                    .fromApp(filter.appId())
+                    .withPriority(filter.priority());
+
+            if (filter.permanent()) {
+                ruleBuilder.makePermanent();
+            } else {
+                ruleBuilder.makeTemporary(filter.timeout());
+            }
+
+            installObjective(ruleBuilder, filter);
+        }
+
+        @Override
+        public void forward(ForwardingObjective fwd) {
+            TrafficSelector selector = fwd.selector();
+
+            if (fwd.treatment() != null) {
+                // Deal with SPECIFIC and VERSATILE in the same manner.
+                FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                        .forDevice(deviceId)
+                        .withSelector(selector)
+                        .fromApp(fwd.appId())
+                        .withPriority(fwd.priority())
+                        .withTreatment(fwd.treatment());
+
+                if (fwd.permanent()) {
+                    ruleBuilder.makePermanent();
+                } else {
+                    ruleBuilder.makeTemporary(fwd.timeout());
+                }
+                installObjective(ruleBuilder, fwd);
+
+            } else {
+                NextObjective nextObjective = pendingNext.getIfPresent(fwd.nextId());
+                if (nextObjective != null) {
+                    pendingNext.invalidate(fwd.nextId());
+                    nextObjective.next().forEach(treat -> {
+                        FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                                .forDevice(deviceId)
+                                .withSelector(selector)
+                                .fromApp(fwd.appId())
+                                .withPriority(fwd.priority())
+                                .withTreatment(treat);
+
+                        if (fwd.permanent()) {
+                            ruleBuilder.makePermanent();
+                        } else {
+                            ruleBuilder.makeTemporary(fwd.timeout());
+                        }
+                        installObjective(ruleBuilder, fwd);
+                    });
+                } else {
+                    fwd.context().ifPresent(c -> c.onError(fwd,
+                                                           ObjectiveError.GROUPMISSING));
+                }
+            }
+        }
+
+        private void installObjective(FlowRule.Builder ruleBuilder, Objective objective) {
+            FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder();
+            switch (objective.op()) {
+
+                case ADD:
+                    flowBuilder.add(ruleBuilder.build());
+                    break;
+                case REMOVE:
+                    flowBuilder.remove(ruleBuilder.build());
+                    break;
+                default:
+                    log.warn("Unknown operation {}", objective.op());
+            }
+
+            flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() {
+                @Override
+                public void onSuccess(FlowRuleOperations ops) {
+                    objective.context().ifPresent(context -> context.onSuccess(objective));
+                }
+
+                @Override
+                public void onError(FlowRuleOperations ops) {
+                    objective.context()
+                            .ifPresent(context ->
+                                               context.onError(objective,
+                                                                  ObjectiveError.FLOWINSTALLATIONFAILED));
+                }
+            }));
+        }
+
+        @Override
+        public void next(NextObjective nextObjective) {
+
+            pendingNext.put(nextObjective.id(), nextObjective);
+            flowObjectiveStore.putNextGroup(nextObjective.id(),
+                                            new SingleGroup(
+                                                    new DefaultGroupKey(
+                                                            appKryo.serialize(nextObjective.id()))));
+            nextObjective.context().ifPresent(context -> context.onSuccess(nextObjective));
+        }
+
+        @Override
+        public List<String> getNextMappings(NextGroup nextGroup) {
+            // Default single table pipeline does not use nextObjectives or groups
+            return null;
+        }
+
+        private class SingleGroup implements NextGroup {
+
+            private final GroupKey key;
+
+            public SingleGroup(GroupKey key) {
+                this.key = key;
+            }
+
+            public GroupKey key() {
+                return key;
+            }
+
+            @Override
+            public byte[] data() {
+                return appKryo.serialize(key);
+            }
+        }
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
new file mode 100644
index 0000000..f501696
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManager.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleOperation;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
+
+/**
+ * Flow rule service implementation built on the virtual network service.
+ */
+public class VirtualNetworkFlowRuleManager
+        extends AbstractVirtualListenerManager<FlowRuleEvent, FlowRuleListener>
+        implements FlowRuleService {
+
+    private static final String VIRTUAL_FLOW_OP_TOPIC = "virtual-flow-ops-ids";
+    private static final String THREAD_GROUP_NAME = "onos/virtual-flowservice";
+    private static final String DEVICE_INSTALLER_PATTERN = "device-installer-%d";
+    private static final String OPERATION_PATTERN = "operations-%d";
+    public static final String FLOW_RULE_NULL = "FlowRule cannot be null";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final VirtualNetworkFlowRuleStore store;
+    private final DeviceService deviceService;
+
+    protected ExecutorService deviceInstallers =
+            Executors.newFixedThreadPool(32,
+                                         groupedThreads(THREAD_GROUP_NAME,
+                                                        DEVICE_INSTALLER_PATTERN, log));
+    protected ExecutorService operationsService =
+            Executors.newFixedThreadPool(32,
+                                         groupedThreads(THREAD_GROUP_NAME,
+                                                        OPERATION_PATTERN, log));
+    private IdGenerator idGenerator;
+
+    private final Map<Long, FlowOperationsProcessor> pendingFlowOperations = new ConcurrentHashMap<>();
+
+    private VirtualProviderRegistryService providerRegistryService = null;
+    private InternalFlowRuleProviderService innerProviderService = null;
+
+    private final FlowRuleStoreDelegate storeDelegate;
+
+    /**
+     * Creates a new VirtualNetworkFlowRuleService object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+    public VirtualNetworkFlowRuleManager(VirtualNetworkService virtualNetworkManager,
+                                         NetworkId networkId) {
+        super(virtualNetworkManager, networkId, FlowRuleEvent.class);
+
+        store = serviceDirectory.get(VirtualNetworkFlowRuleStore.class);
+
+        idGenerator = serviceDirectory.get(CoreService.class)
+                .getIdGenerator(VIRTUAL_FLOW_OP_TOPIC + networkId().toString());
+        providerRegistryService =
+                serviceDirectory.get(VirtualProviderRegistryService.class);
+        innerProviderService = new InternalFlowRuleProviderService();
+        providerRegistryService.registerProviderService(networkId(), innerProviderService);
+
+        this.deviceService = manager.get(networkId, DeviceService.class);
+        this.storeDelegate = new InternalStoreDelegate();
+        store.setDelegate(networkId, this.storeDelegate);
+    }
+
+    @Override
+    public int getFlowRuleCount() {
+        return store.getFlowRuleCount(networkId());
+    }
+
+    @Override
+    public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+        return store.getFlowEntries(networkId(), deviceId);
+    }
+
+    @Override
+    public void applyFlowRules(FlowRule... flowRules) {
+        FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+        for (FlowRule flowRule : flowRules) {
+            builder.add(flowRule);
+        }
+        apply(builder.build());
+    }
+
+    @Override
+    public void purgeFlowRules(DeviceId deviceId) {
+        store.purgeFlowRule(networkId(), deviceId);
+    }
+
+    @Override
+    public void removeFlowRules(FlowRule... flowRules) {
+        FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+        for (FlowRule flowRule : flowRules) {
+            builder.remove(flowRule);
+        }
+        apply(builder.build());
+    }
+
+    @Override
+    public void removeFlowRulesById(ApplicationId id) {
+        removeFlowRules(Iterables.toArray(getFlowRulesById(id), FlowRule.class));
+    }
+
+    @Override
+    public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
+        DeviceService deviceService = manager.get(networkId(), DeviceService.class);
+
+        Set<FlowRule> flowEntries = Sets.newHashSet();
+        for (Device d : deviceService.getDevices()) {
+            for (FlowEntry flowEntry : store.getFlowEntries(networkId(), d.id())) {
+                if (flowEntry.appId() == id.id()) {
+                    flowEntries.add(flowEntry);
+                }
+            }
+        }
+        return flowEntries;
+    }
+
+    @Override
+    public Iterable<FlowEntry> getFlowEntriesById(ApplicationId id) {
+        DeviceService deviceService = manager.get(networkId(), DeviceService.class);
+
+        Set<FlowEntry> flowEntries = Sets.newHashSet();
+        for (Device d : deviceService.getDevices()) {
+            for (FlowEntry flowEntry : store.getFlowEntries(networkId(), d.id())) {
+                if (flowEntry.appId() == id.id()) {
+                    flowEntries.add(flowEntry);
+                }
+            }
+        }
+        return flowEntries;
+    }
+
+    @Override
+    public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) {
+        DeviceService deviceService = manager.get(networkId(), DeviceService.class);
+
+        Set<FlowRule> matches = Sets.newHashSet();
+        long toLookUp = ((long) appId.id() << 16) | groupId;
+        for (Device d : deviceService.getDevices()) {
+            for (FlowEntry flowEntry : store.getFlowEntries(networkId(), d.id())) {
+                if ((flowEntry.id().value() >>> 32) == toLookUp) {
+                    matches.add(flowEntry);
+                }
+            }
+        }
+        return matches;
+    }
+
+    @Override
+    public void apply(FlowRuleOperations ops) {
+        operationsService.execute(new FlowOperationsProcessor(ops));
+    }
+
+    @Override
+    public Iterable<TableStatisticsEntry> getFlowTableStatistics(DeviceId deviceId) {
+        return store.getTableStatistics(networkId(), deviceId);
+    }
+
+    private static FlowRuleBatchEntry.FlowRuleOperation mapOperationType(FlowRuleOperation.Type input) {
+        switch (input) {
+            case ADD:
+                return FlowRuleBatchEntry.FlowRuleOperation.ADD;
+            case MODIFY:
+                return FlowRuleBatchEntry.FlowRuleOperation.MODIFY;
+            case REMOVE:
+                return FlowRuleBatchEntry.FlowRuleOperation.REMOVE;
+            default:
+                throw new UnsupportedOperationException("Unknown flow rule type " + input);
+        }
+    }
+
+    private class FlowOperationsProcessor implements Runnable {
+        // Immutable
+        private final FlowRuleOperations fops;
+
+        // Mutable
+        private final List<Set<FlowRuleOperation>> stages;
+        private final Set<DeviceId> pendingDevices = new HashSet<>();
+        private boolean hasFailed = false;
+
+        FlowOperationsProcessor(FlowRuleOperations ops) {
+            this.stages = Lists.newArrayList(ops.stages());
+            this.fops = ops;
+        }
+
+        @Override
+        public synchronized void run() {
+            if (!stages.isEmpty()) {
+                process(stages.remove(0));
+            } else if (!hasFailed) {
+                fops.callback().onSuccess(fops);
+            }
+        }
+
+        private void process(Set<FlowRuleOperation> ops) {
+            Multimap<DeviceId, FlowRuleBatchEntry> perDeviceBatches = ArrayListMultimap.create();
+
+            for (FlowRuleOperation op : ops) {
+                perDeviceBatches.put(op.rule().deviceId(),
+                                     new FlowRuleBatchEntry(mapOperationType(op.type()), op.rule()));
+            }
+            pendingDevices.addAll(perDeviceBatches.keySet());
+
+            for (DeviceId deviceId : perDeviceBatches.keySet()) {
+                long id = idGenerator.getNewId();
+                final FlowRuleBatchOperation b = new FlowRuleBatchOperation(perDeviceBatches.get(deviceId),
+                                                                            deviceId, id);
+                pendingFlowOperations.put(id, this);
+                deviceInstallers.execute(() -> store.storeBatch(networkId(), b));
+            }
+        }
+
+        synchronized void satisfy(DeviceId devId) {
+            pendingDevices.remove(devId);
+            if (pendingDevices.isEmpty()) {
+                operationsService.execute(this);
+            }
+        }
+
+        synchronized void fail(DeviceId devId, Set<? extends FlowRule> failures) {
+            hasFailed = true;
+            pendingDevices.remove(devId);
+            if (pendingDevices.isEmpty()) {
+                operationsService.execute(this);
+            }
+
+            FlowRuleOperations.Builder failedOpsBuilder = FlowRuleOperations.builder();
+            failures.forEach(failedOpsBuilder::add);
+
+            fops.callback().onError(failedOpsBuilder.build());
+        }
+    }
+
+    private final class InternalFlowRuleProviderService
+            extends AbstractVirtualProviderService<VirtualFlowRuleProvider>
+            implements VirtualFlowRuleProviderService {
+
+        final Map<FlowEntry, Long> firstSeen = Maps.newConcurrentMap();
+        final Map<FlowEntry, Long> lastSeen = Maps.newConcurrentMap();
+
+        private InternalFlowRuleProviderService() {
+            //TODO: find a proper virtual provider.
+            Set<ProviderId> providerIds =
+                    providerRegistryService.getProvidersByService(this);
+            ProviderId providerId = providerIds.stream().findFirst().get();
+            VirtualFlowRuleProvider provider = (VirtualFlowRuleProvider)
+                    providerRegistryService.getProvider(providerId);
+            setProvider(provider);
+        }
+
+        @Override
+        public void flowRemoved(FlowEntry flowEntry) {
+            checkNotNull(flowEntry, FLOW_RULE_NULL);
+            checkValidity();
+
+            lastSeen.remove(flowEntry);
+            firstSeen.remove(flowEntry);
+            FlowEntry stored = store.getFlowEntry(networkId(), flowEntry);
+            if (stored == null) {
+                log.debug("Rule already evicted from store: {}", flowEntry);
+                return;
+            }
+            if (flowEntry.reason() == FlowEntry.FlowRemoveReason.HARD_TIMEOUT) {
+                ((DefaultFlowEntry) stored).setState(FlowEntry.FlowEntryState.REMOVED);
+            }
+
+            //FIXME: obtains provider from devices providerId()
+            FlowRuleEvent event = null;
+            switch (stored.state()) {
+                case ADDED:
+                case PENDING_ADD:
+                    provider().applyFlowRule(networkId(), stored);
+                    break;
+                case PENDING_REMOVE:
+                case REMOVED:
+                    event = store.removeFlowRule(networkId(), stored);
+                    break;
+                default:
+                    break;
+
+            }
+            if (event != null) {
+                log.debug("Flow {} removed", flowEntry);
+                post(event);
+            }
+        }
+
+        private void flowMissing(FlowEntry flowRule) {
+            checkNotNull(flowRule, FLOW_RULE_NULL);
+            checkValidity();
+
+            FlowRuleEvent event = null;
+            switch (flowRule.state()) {
+                case PENDING_REMOVE:
+                case REMOVED:
+                    event = store.removeFlowRule(networkId(), flowRule);
+                    break;
+                case ADDED:
+                case PENDING_ADD:
+                    event = store.pendingFlowRule(networkId(), flowRule);
+
+                    try {
+                        provider().applyFlowRule(networkId(), flowRule);
+                    } catch (UnsupportedOperationException e) {
+                        log.warn(e.getMessage());
+                        if (flowRule instanceof DefaultFlowEntry) {
+                            //FIXME modification of "stored" flow entry outside of store
+                            ((DefaultFlowEntry) flowRule).setState(FlowEntry.FlowEntryState.FAILED);
+                        }
+                    }
+                    break;
+                default:
+                    log.debug("Flow {} has not been installed.", flowRule);
+            }
+
+            if (event != null) {
+                log.debug("Flow {} removed", flowRule);
+                post(event);
+            }
+        }
+
+        private void extraneousFlow(FlowRule flowRule) {
+            checkNotNull(flowRule, FLOW_RULE_NULL);
+            checkValidity();
+
+            provider().removeFlowRule(networkId(), flowRule);
+            log.debug("Flow {} is on switch but not in store.", flowRule);
+        }
+
+        private void flowAdded(FlowEntry flowEntry) {
+            checkNotNull(flowEntry, FLOW_RULE_NULL);
+
+            if (checkRuleLiveness(flowEntry, store.getFlowEntry(networkId(), flowEntry))) {
+                FlowRuleEvent event = store.addOrUpdateFlowRule(networkId(), flowEntry);
+                if (event == null) {
+                    log.debug("No flow store event generated.");
+                } else {
+                    log.trace("Flow {} {}", flowEntry, event.type());
+                    post(event);
+                }
+            } else {
+                log.debug("Removing flow rules....");
+                removeFlowRules(flowEntry);
+            }
+        }
+
+        private boolean checkRuleLiveness(FlowEntry swRule, FlowEntry storedRule) {
+            if (storedRule == null) {
+                return false;
+            }
+            if (storedRule.isPermanent()) {
+                return true;
+            }
+
+            final long timeout = storedRule.timeout() * 1000L;
+            final long currentTime = System.currentTimeMillis();
+
+            // Checking flow with hardTimeout
+            if (storedRule.hardTimeout() != 0) {
+                if (!firstSeen.containsKey(storedRule)) {
+                    // First time rule adding
+                    firstSeen.put(storedRule, currentTime);
+                } else {
+                    Long first = firstSeen.get(storedRule);
+                    final long hardTimeout = storedRule.hardTimeout() * 1000L;
+                    if ((currentTime - first) > hardTimeout) {
+                        return false;
+                    }
+                }
+            }
+
+            if (storedRule.packets() != swRule.packets()) {
+                lastSeen.put(storedRule, currentTime);
+                return true;
+            }
+            if (!lastSeen.containsKey(storedRule)) {
+                // checking for the first time
+                lastSeen.put(storedRule, storedRule.lastSeen());
+                // Use following if lastSeen attr. was removed.
+                //lastSeen.put(storedRule, currentTime);
+            }
+            Long last = lastSeen.get(storedRule);
+
+            // concurrently removed? let the liveness check fail
+            return last != null && (currentTime - last) <= timeout;
+        }
+
+        @Override
+        public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
+            pushFlowMetricsInternal(deviceId, flowEntries, true);
+        }
+
+        @Override
+        public void pushFlowMetricsWithoutFlowMissing(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
+            pushFlowMetricsInternal(deviceId, flowEntries, false);
+        }
+
+        private void pushFlowMetricsInternal(DeviceId deviceId, Iterable<FlowEntry> flowEntries,
+                                             boolean useMissingFlow) {
+            Map<FlowEntry, FlowEntry> storedRules = Maps.newHashMap();
+            store.getFlowEntries(networkId(), deviceId).forEach(f -> storedRules.put(f, f));
+
+            for (FlowEntry rule : flowEntries) {
+                try {
+                    FlowEntry storedRule = storedRules.remove(rule);
+                    if (storedRule != null) {
+                        if (storedRule.id().equals(rule.id())) {
+                            // we both have the rule, let's update some info then.
+                            flowAdded(rule);
+                        } else {
+                            // the two rules are not an exact match - remove the
+                            // switch's rule and install our rule
+                            extraneousFlow(rule);
+                            flowMissing(storedRule);
+                        }
+                    }
+                } catch (Exception e) {
+                    log.debug("Can't process added or extra rule {}", e.getMessage());
+                }
+            }
+
+            // DO NOT reinstall
+            if (useMissingFlow) {
+                for (FlowEntry rule : storedRules.keySet()) {
+                    try {
+                        // there are rules in the store that aren't on the switch
+                        log.debug("Adding rule in store, but not on switch {}", rule);
+                        flowMissing(rule);
+                    } catch (Exception e) {
+                        log.debug("Can't add missing flow rule:", e);
+                    }
+                }
+            }
+        }
+
+        public void batchOperationCompleted(long batchId, CompletedBatchOperation operation) {
+            store.batchOperationComplete(networkId(), FlowRuleBatchEvent.completed(
+                    new FlowRuleBatchRequest(batchId, Collections.emptySet()),
+                    operation
+            ));
+        }
+
+        @Override
+        public void pushTableStatistics(DeviceId deviceId,
+                                        List<TableStatisticsEntry> tableStats) {
+            store.updateTableStatistics(networkId(), deviceId, tableStats);
+        }
+    }
+
+    // Store delegate to re-post events emitted from the store.
+    private class InternalStoreDelegate implements FlowRuleStoreDelegate {
+
+        // TODO: Right now we only dispatch events at individual flowEntry level.
+        // It may be more efficient for also dispatch events as a batch.
+        @Override
+        public void notify(FlowRuleBatchEvent event) {
+            final FlowRuleBatchRequest request = event.subject();
+            switch (event.type()) {
+                case BATCH_OPERATION_REQUESTED:
+                    // Request has been forwarded to MASTER Node, and was
+                    request.ops().forEach(
+                            op -> {
+                                switch (op.operator()) {
+                                    case ADD:
+                                        post(new FlowRuleEvent(RULE_ADD_REQUESTED, op.target()));
+                                        break;
+                                    case REMOVE:
+                                        post(new FlowRuleEvent(RULE_REMOVE_REQUESTED, op.target()));
+                                        break;
+                                    case MODIFY:
+                                        //TODO: do something here when the time comes.
+                                        break;
+                                    default:
+                                        log.warn("Unknown flow operation operator: {}", op.operator());
+                                }
+                            }
+                    );
+
+                    DeviceId deviceId = event.deviceId();
+                    FlowRuleBatchOperation batchOperation = request.asBatchOperation(deviceId);
+
+                    VirtualFlowRuleProvider provider = innerProviderService.provider();
+                    if (provider != null) {
+                        provider.executeBatch(networkId, batchOperation);
+                    }
+
+                    break;
+
+                case BATCH_OPERATION_COMPLETED:
+                    FlowOperationsProcessor fops = pendingFlowOperations.remove(
+                            event.subject().batchId());
+                    if (fops == null) {
+                       return;
+                    }
+
+                    if (event.result().isSuccess()) {
+                            fops.satisfy(event.deviceId());
+                    } else {
+                        fops.fail(event.deviceId(), event.result().failedItems());
+                    }
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManager.java
new file mode 100644
index 0000000..5fed4a8
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManager.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkGroupStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Group service implementation built on the virtual network service.
+ */
+public class VirtualNetworkGroupManager
+        extends AbstractVirtualListenerManager<GroupEvent, GroupListener>
+        implements GroupService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final VirtualNetworkGroupStore store;
+
+    private VirtualProviderRegistryService providerRegistryService = null;
+    private VirtualGroupProviderService innerProviderService;
+    private InternalStoreDelegate storeDelegate;
+    private DeviceService deviceService;
+
+    //TODO: make this configurable
+    private boolean purgeOnDisconnection = false;
+
+    public VirtualNetworkGroupManager(VirtualNetworkService manager, NetworkId networkId) {
+        super(manager, networkId, GroupEvent.class);
+
+        store = serviceDirectory.get(VirtualNetworkGroupStore.class);
+        deviceService = manager.get(networkId, DeviceService.class);
+
+        providerRegistryService =
+                serviceDirectory.get(VirtualProviderRegistryService.class);
+        innerProviderService = new InternalGroupProviderService();
+        providerRegistryService.registerProviderService(networkId(), innerProviderService);
+
+        this.storeDelegate = new InternalStoreDelegate();
+        store.setDelegate(networkId, this.storeDelegate);
+
+        log.info("Started");
+    }
+
+    @Override
+    public void addGroup(GroupDescription groupDesc) {
+        store.storeGroupDescription(networkId(), groupDesc);
+    }
+
+    @Override
+    public Group getGroup(DeviceId deviceId, GroupKey appCookie) {
+        return store.getGroup(networkId(), deviceId, appCookie);
+    }
+
+    @Override
+    public void addBucketsToGroup(DeviceId deviceId, GroupKey oldCookie, GroupBuckets buckets,
+                                  GroupKey newCookie, ApplicationId appId) {
+        store.updateGroupDescription(networkId(),
+                                     deviceId,
+                                     oldCookie,
+                                     VirtualNetworkGroupStore.UpdateType.ADD,
+                                     buckets,
+                                     newCookie);
+    }
+
+    @Override
+    public void removeBucketsFromGroup(DeviceId deviceId, GroupKey oldCookie,
+                                       GroupBuckets buckets, GroupKey newCookie,
+                                       ApplicationId appId) {
+        store.updateGroupDescription(networkId(),
+                                     deviceId,
+                                     oldCookie,
+                                     VirtualNetworkGroupStore.UpdateType.REMOVE,
+                                     buckets,
+                                     newCookie);
+
+    }
+
+    @Override
+    public void setBucketsForGroup(DeviceId deviceId,
+                                   GroupKey oldCookie,
+                                   GroupBuckets buckets,
+                                   GroupKey newCookie,
+                                   ApplicationId appId) {
+        store.updateGroupDescription(networkId(),
+                                     deviceId,
+                                     oldCookie,
+                                     VirtualNetworkGroupStore.UpdateType.SET,
+                                     buckets,
+                                     newCookie);
+    }
+
+    @Override
+    public void purgeGroupEntries(DeviceId deviceId) {
+        store.purgeGroupEntry(networkId(), deviceId);
+    }
+
+    @Override
+    public void purgeGroupEntries() {
+        store.purgeGroupEntries(networkId());
+    }
+
+    @Override
+    public void removeGroup(DeviceId deviceId, GroupKey appCookie, ApplicationId appId) {
+        store.deleteGroupDescription(networkId(), deviceId, appCookie);
+    }
+
+    @Override
+    public Iterable<Group> getGroups(DeviceId deviceId, ApplicationId appId) {
+        return store.getGroups(networkId(), deviceId);
+    }
+
+    @Override
+    public Iterable<Group> getGroups(DeviceId deviceId) {
+        return store.getGroups(networkId(), deviceId);
+    }
+
+    private class InternalGroupProviderService
+            extends AbstractVirtualProviderService<VirtualGroupProvider>
+            implements VirtualGroupProviderService {
+
+        protected InternalGroupProviderService() {
+            Set<ProviderId> providerIds =
+                    providerRegistryService.getProvidersByService(this);
+            ProviderId providerId = providerIds.stream().findFirst().get();
+            VirtualGroupProvider provider = (VirtualGroupProvider)
+                    providerRegistryService.getProvider(providerId);
+            setProvider(provider);
+        }
+
+        @Override
+        public void groupOperationFailed(DeviceId deviceId,
+                                         GroupOperation operation) {
+            store.groupOperationFailed(networkId(), deviceId, operation);
+        }
+
+        @Override
+        public void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries) {
+            log.trace("Received group metrics from device {}", deviceId);
+            checkValidity();
+            store.pushGroupMetrics(networkId(), deviceId, groupEntries);
+        }
+
+        @Override
+        public void notifyOfFailovers(Collection<Group> failoverGroups) {
+            store.notifyOfFailovers(networkId(), failoverGroups);
+        }
+    }
+
+    private class InternalStoreDelegate implements GroupStoreDelegate {
+        @Override
+        public void notify(GroupEvent event) {
+            final Group group = event.subject();
+            VirtualGroupProvider groupProvider = innerProviderService.provider();
+            GroupOperations groupOps = null;
+            switch (event.type()) {
+                case GROUP_ADD_REQUESTED:
+                    log.debug("GROUP_ADD_REQUESTED for Group {} on device {}",
+                              group.id(), group.deviceId());
+                    GroupOperation groupAddOp = GroupOperation.
+                            createAddGroupOperation(group.id(),
+                                                    group.type(),
+                                                    group.buckets());
+                    groupOps = new GroupOperations(
+                            Collections.singletonList(groupAddOp));
+                    groupProvider.performGroupOperation(networkId(), group.deviceId(),
+                                                        groupOps);
+                    break;
+
+                case GROUP_UPDATE_REQUESTED:
+                    log.debug("GROUP_UPDATE_REQUESTED for Group {} on device {}",
+                              group.id(), group.deviceId());
+                    GroupOperation groupModifyOp = GroupOperation.
+                            createModifyGroupOperation(group.id(),
+                                                       group.type(),
+                                                       group.buckets());
+                    groupOps = new GroupOperations(
+                            Collections.singletonList(groupModifyOp));
+                    groupProvider.performGroupOperation(networkId(), group.deviceId(),
+                                                        groupOps);
+                    break;
+
+                case GROUP_REMOVE_REQUESTED:
+                    log.debug("GROUP_REMOVE_REQUESTED for Group {} on device {}",
+                              group.id(), group.deviceId());
+                    GroupOperation groupDeleteOp = GroupOperation.
+                            createDeleteGroupOperation(group.id(),
+                                                       group.type());
+                    groupOps = new GroupOperations(
+                            Collections.singletonList(groupDeleteOp));
+                    groupProvider.performGroupOperation(networkId(), group.deviceId(),
+                                                        groupOps);
+                    break;
+
+                case GROUP_ADDED:
+                case GROUP_UPDATED:
+                case GROUP_REMOVED:
+                case GROUP_ADD_FAILED:
+                case GROUP_UPDATE_FAILED:
+                case GROUP_REMOVE_FAILED:
+                case GROUP_BUCKET_FAILOVER:
+                    post(event);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            switch (event.type()) {
+                case DEVICE_REMOVED:
+                case DEVICE_AVAILABILITY_CHANGED:
+                    DeviceId deviceId = event.subject().id();
+                    if (!deviceService.isAvailable(deviceId)) {
+                        log.debug("Device {} became un available; clearing initial audit status",
+                                  event.type(), event.subject().id());
+                        store.deviceInitialAuditCompleted(networkId(), event.subject().id(), false);
+
+                        if (purgeOnDisconnection) {
+                            store.purgeGroupEntry(networkId(), deviceId);
+                        }
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManager.java
new file mode 100644
index 0000000..5d14d4a
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManager.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Host service implementation built on the virtual network service.
+ */
+public class VirtualNetworkHostManager
+        extends AbstractVirtualListenerManager<HostEvent, HostListener>
+        implements HostService {
+
+    private static final String HOST_NULL = "Host ID cannot be null";
+
+    /**
+     * Creates a new virtual network host service object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+    public VirtualNetworkHostManager(VirtualNetworkService virtualNetworkManager,
+                                     NetworkId networkId) {
+        super(virtualNetworkManager, networkId, HostEvent.class);
+    }
+
+
+    @Override
+    public int getHostCount() {
+        return manager.getVirtualHosts(this.networkId()).size();
+    }
+
+    @Override
+    public Iterable<Host> getHosts() {
+        return getHostsColl();
+    }
+
+    @Override
+    public Host getHost(HostId hostId) {
+        checkNotNull(hostId, HOST_NULL);
+        Optional<VirtualHost> foundHost =
+                manager.getVirtualHosts(this.networkId())
+                .stream()
+                .filter(host -> hostId.equals(host.id()))
+                .findFirst();
+        if (foundHost.isPresent()) {
+            return foundHost.get();
+        }
+        return null;
+    }
+
+    /**
+     * Gets a collection of virtual hosts.
+     *
+     * @return collection of virtual hosts.
+     */
+    private Collection<Host> getHostsColl() {
+        return manager.getVirtualHosts(this.networkId())
+                .stream().collect(Collectors.toSet());
+    }
+
+    /**
+     * Filters specified collection.
+     *
+     * @param collection collection of hosts to filter
+     * @param predicate condition to filter on
+     * @return collection of virtual hosts that satisfy the filter condition
+     */
+    private Set<Host> filter(Collection<Host> collection, Predicate<Host> predicate) {
+        return collection.stream().filter(predicate).collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Host> getHostsByVlan(VlanId vlanId) {
+        checkNotNull(vlanId, "VLAN identifier cannot be null");
+        return filter(getHostsColl(), host -> Objects.equals(host.vlan(), vlanId));
+    }
+
+    @Override
+    public Set<Host> getHostsByMac(MacAddress mac) {
+        checkNotNull(mac, "MAC address cannot be null");
+        return filter(getHostsColl(), host -> Objects.equals(host.mac(), mac));
+    }
+
+    @Override
+    public Set<Host> getHostsByIp(IpAddress ip) {
+        checkNotNull(ip, "IP address cannot be null");
+        return filter(getHostsColl(), host -> host.ipAddresses().contains(ip));
+    }
+
+    @Override
+    public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, "Connect point cannot be null");
+        return filter(getHostsColl(), host -> host.location().equals(connectPoint));
+    }
+
+    @Override
+    public Set<Host> getConnectedHosts(DeviceId deviceId) {
+        checkNotNull(deviceId, "Device identifier cannot be null");
+        return filter(getHostsColl(), host -> host.location().deviceId().equals(deviceId));
+    }
+
+    @Override
+    public void startMonitoringIp(IpAddress ip) {
+        //TODO check what needs to be done here
+    }
+
+    @Override
+    public void stopMonitoringIp(IpAddress ip) {
+        //TODO check what needs to be done here
+    }
+
+    @Override
+    public void requestMac(IpAddress ip) {
+        //TODO check what needs to be done here
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManager.java
new file mode 100644
index 0000000..f4c1137
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManager.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onlab.util.Tools;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.VnetService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualFinalIntentProcessPhase;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentInstallCoordinator;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentAccumulator;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentCompilerRegistry;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentInstallerRegistry;
+import org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentSkipped;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentBatchDelegate;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.resource.ResourceConsumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.*;
+import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
+import static org.onlab.util.BoundedThreadPool.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase.newInitialPhase;
+import static org.onosproject.net.intent.IntentState.FAILED;
+
+/**
+ * Intent service implementation built on the virtual network service.
+ */
+public class VirtualNetworkIntentManager
+        extends AbstractVirtualListenerManager<IntentEvent, IntentListener>
+        implements IntentService, VnetService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final int DEFAULT_NUM_THREADS = 12;
+    private int numThreads = DEFAULT_NUM_THREADS;
+
+    private static final String NETWORK_ID_NULL = "Network ID cannot be null";
+    private static final String DEVICE_NULL = "Device cannot be null";
+    private static final String INTENT_NULL = "Intent cannot be null";
+    private static final String KEY_NULL = "Key cannot be null";
+    private static final String APP_ID_NULL = "Intent app identifier cannot be null";
+    private static final String INTENT_KEY_NULL = "Intent key cannot be null";
+    private static final String CP_NULL = "Connect Point cannot be null";
+
+    //FIXME: Tracker service for vnet.
+
+    //ONOS core services
+    protected VirtualNetworkStore virtualNetworkStore;
+    protected VirtualNetworkIntentStore intentStore;
+
+    //Virtual network services
+    protected GroupService groupService;
+
+    private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
+    private final InternalIntentProcessor processor = new InternalIntentProcessor();
+    private final IntentStoreDelegate delegate = new InternalStoreDelegate();
+    private final VirtualIntentCompilerRegistry compilerRegistry =
+            VirtualIntentCompilerRegistry.getInstance();
+    private final VirtualIntentInstallerRegistry installerRegistry =
+            VirtualIntentInstallerRegistry.getInstance();
+    private final VirtualIntentAccumulator accumulator =
+            new VirtualIntentAccumulator(batchDelegate);
+
+    private VirtualIntentInstallCoordinator installCoordinator;
+    private ExecutorService batchExecutor;
+    private ExecutorService workerExecutor;
+
+    /**
+     * Creates a new VirtualNetworkIntentService object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+    public VirtualNetworkIntentManager(VirtualNetworkService virtualNetworkManager,
+                                       NetworkId networkId) {
+
+        super(virtualNetworkManager, networkId, IntentEvent.class);
+
+        this.virtualNetworkStore = serviceDirectory.get(VirtualNetworkStore.class);
+        this.intentStore = serviceDirectory.get(VirtualNetworkIntentStore.class);
+
+        this.groupService = manager.get(networkId, GroupService.class);
+
+        intentStore.setDelegate(networkId, delegate);
+        batchExecutor = newSingleThreadExecutor(groupedThreads("onos/intent", "batch", log));
+        workerExecutor = newFixedThreadPool(numThreads, groupedThreads("onos/intent", "worker-%d", log));
+
+        installCoordinator = new VirtualIntentInstallCoordinator(networkId, installerRegistry, intentStore);
+        log.info("Started");
+
+    }
+
+    @Override
+    public void submit(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        checkState(intent instanceof VirtualNetworkIntent, "Only VirtualNetworkIntent is supported.");
+        checkArgument(validateIntent((VirtualNetworkIntent) intent), "Invalid Intent");
+
+        IntentData data = IntentData.submit(intent);
+        intentStore.addPending(networkId, data);
+    }
+
+    /**
+     * Returns true if the virtual network intent is valid.
+     *
+     * @param intent virtual network intent
+     * @return true if intent is valid
+     */
+    private boolean validateIntent(VirtualNetworkIntent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        checkNotNull(intent.networkId(), NETWORK_ID_NULL);
+        checkNotNull(intent.appId(), APP_ID_NULL);
+        checkNotNull(intent.key(), INTENT_KEY_NULL);
+        ConnectPoint ingressPoint = intent.ingressPoint();
+        ConnectPoint egressPoint = intent.egressPoint();
+
+        return (validateConnectPoint(ingressPoint) && validateConnectPoint(egressPoint));
+    }
+
+    /**
+     * Returns true if the connect point is valid.
+     *
+     * @param connectPoint connect point
+     * @return true if connect point is valid
+     */
+    private boolean validateConnectPoint(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CP_NULL);
+        Port port = getPort(connectPoint.deviceId(), connectPoint.port());
+        return port != null;
+    }
+
+    /**
+     * Returns the virtual port for the given device identifier and port number.
+     *
+     * @param deviceId   virtual device identifier
+     * @param portNumber virtual port number
+     * @return virtual port
+     */
+    private Port getPort(DeviceId deviceId, PortNumber portNumber) {
+        checkNotNull(deviceId, DEVICE_NULL);
+
+        Optional<VirtualPort> foundPort = manager.getVirtualPorts(this.networkId(), deviceId)
+                .stream()
+                .filter(port -> port.number().equals(portNumber))
+                .findFirst();
+        if (foundPort.isPresent()) {
+            return foundPort.get();
+        }
+        return null;
+    }
+
+    @Override
+    public void withdraw(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        IntentData data = IntentData.withdraw(intent);
+        intentStore.addPending(networkId, data);
+    }
+
+    @Override
+    public void purge(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+
+        IntentData data = IntentData.purge(intent);
+        intentStore.addPending(networkId, data);
+
+        // remove associated group if there is one
+        // FIXME: Remove P2P intent for vnets
+    }
+
+    @Override
+    public Intent getIntent(Key key) {
+        checkNotNull(key, KEY_NULL);
+        return intentStore.getIntent(networkId, key);
+    }
+
+    @Override
+    public Iterable<Intent> getIntents() {
+        return intentStore.getIntents(networkId);
+    }
+
+    @Override
+    public void addPending(IntentData intentData) {
+        checkNotNull(intentData, INTENT_NULL);
+        //TODO we might consider further checking / assertions
+        intentStore.addPending(networkId, intentData);
+    }
+
+    @Override
+    public Iterable<IntentData> getIntentData() {
+        return intentStore.getIntentData(networkId, false, 0);
+    }
+
+    @Override
+    public long getIntentCount() {
+        return intentStore.getIntentCount(networkId);
+    }
+
+    @Override
+    public IntentState getIntentState(Key intentKey) {
+        checkNotNull(intentKey, KEY_NULL);
+        return intentStore.getIntentState(networkId, intentKey);
+    }
+
+    @Override
+    public List<Intent> getInstallableIntents(Key intentKey) {
+        return intentStore.getInstallableIntents(networkId, intentKey);
+    }
+
+    @Override
+    public boolean isLocal(Key intentKey) {
+        return intentStore.isMaster(networkId, intentKey);
+    }
+
+    @Override
+    public Iterable<Intent> getPending() {
+        return intentStore.getPending(networkId);
+    }
+
+    // Store delegate to re-post events emitted from the store.
+    private class InternalStoreDelegate implements IntentStoreDelegate {
+        @Override
+        public void notify(IntentEvent event) {
+            post(event);
+            switch (event.type()) {
+                case WITHDRAWN:
+                    //FIXME: release resources
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        @Override
+        public void process(IntentData data) {
+            accumulator.add(data);
+        }
+
+        @Override
+        public void onUpdate(IntentData intentData) {
+            //FIXME: track intent
+        }
+
+        private void releaseResources(Intent intent) {
+            // If a resource group is set on the intent, the resource consumer is
+            // set equal to it. Otherwise it's set to the intent key
+            ResourceConsumer resourceConsumer =
+                    intent.resourceGroup() != null ? intent.resourceGroup() : intent.key();
+
+            // By default the resource doesn't get released
+            boolean removeResource = false;
+
+            if (intent.resourceGroup() == null) {
+                // If the intent doesn't have a resource group, it means the
+                // resource was registered using the intent key, so it can be
+                // released
+                removeResource = true;
+            } else {
+                // When a resource group is set, we make sure there are no other
+                // intents using the same resource group, before deleting the
+                // related resources.
+                Long remainingIntents =
+                        Tools.stream(intentStore.getIntents(networkId))
+                                .filter(i -> {
+                                    return i.resourceGroup() != null
+                                            && i.resourceGroup().equals(intent.resourceGroup());
+                                })
+                                .count();
+                if (remainingIntents == 0) {
+                    removeResource = true;
+                }
+            }
+
+            if (removeResource) {
+                // Release resources allocated to withdrawn intent
+                // FIXME: confirm resources are released
+            }
+        }
+    }
+
+    private class InternalBatchDelegate implements IntentBatchDelegate {
+        @Override
+        public void execute(Collection<IntentData> operations) {
+            log.debug("Execute {} operation(s).", operations.size());
+            log.trace("Execute operations: {}", operations);
+
+            // batchExecutor is single-threaded, so only one batch is in flight at a time
+            CompletableFuture.runAsync(() -> {
+                // process intent until the phase reaches one of the final phases
+                List<CompletableFuture<IntentData>> futures = operations.stream()
+                        .map(data -> {
+                            log.debug("Start processing of {} {}@{}", data.request(), data.key(), data.version());
+                            return data;
+                        })
+                        .map(x -> CompletableFuture.completedFuture(x)
+                                .thenApply(VirtualNetworkIntentManager.this::createInitialPhase)
+                                .thenApplyAsync(VirtualIntentProcessPhase::process, workerExecutor)
+                                .thenApply(VirtualFinalIntentProcessPhase::data)
+                                .exceptionally(e -> {
+                                    // When the future fails, we update the Intent to simulate the failure of
+                                    // the installation/withdrawal phase and we save in the current map. In
+                                    // the next round the CleanUp Thread will pick this Intent again.
+                                    log.warn("Future failed", e);
+                                    log.warn("Intent {} - state {} - request {}",
+                                             x.key(), x.state(), x.request());
+                                    switch (x.state()) {
+                                        case INSTALL_REQ:
+                                        case INSTALLING:
+                                        case WITHDRAW_REQ:
+                                        case WITHDRAWING:
+                                            // TODO should we swtich based on current
+                                            IntentData current = intentStore.getIntentData(networkId, x.key());
+                                            return IntentData.nextState(current, FAILED);
+                                        default:
+                                            return null;
+                                    }
+                                }))
+                        .collect(Collectors.toList());
+
+                // write multiple data to store in order
+                intentStore.batchWrite(networkId, Tools.allOf(futures).join().stream()
+                                         .filter(Objects::nonNull)
+                                         .collect(Collectors.toList()));
+            }, batchExecutor).exceptionally(e -> {
+                log.error("Error submitting batches:", e);
+                // FIXME incomplete Intents should be cleaned up
+                //       (transition to FAILED, etc.)
+
+                // the batch has failed
+                // TODO: maybe we should do more?
+                log.error("Walk the plank, matey...");
+                return null;
+            }).thenRun(accumulator::ready);
+
+        }
+    }
+
+    private VirtualIntentProcessPhase createInitialPhase(IntentData data) {
+        IntentData pending = intentStore.getPendingData(networkId, data.key());
+        if (pending == null || pending.version().isNewerThan(data.version())) {
+            /*
+                If the pending map is null, then this intent was compiled by a
+                previous batch iteration, so we can skip it.
+                If the pending map has a newer request, it will get compiled as
+                part of the next batch, so we can skip it.
+             */
+            return VirtualIntentSkipped.getPhase();
+        }
+        IntentData current = intentStore.getIntentData(networkId, data.key());
+        return newInitialPhase(networkId, processor, data, current);
+    }
+
+    private class InternalIntentProcessor implements VirtualIntentProcessor {
+        @Override
+        public List<Intent> compile(NetworkId networkId,
+                                    Intent intent,
+                                    List<Intent> previousInstallables) {
+            return compilerRegistry.compile(networkId, intent, previousInstallables);
+        }
+
+        @Override
+        public void apply(NetworkId networkId,
+                          Optional<IntentData> toUninstall,
+                          Optional<IntentData> toInstall) {
+
+            installCoordinator.installIntents(toUninstall, toInstall);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManager.java
new file mode 100644
index 0000000..1a1c2bf
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkListener;
+import org.onosproject.net.link.LinkService;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Link service implementation built on the virtual network service.
+ */
+public class VirtualNetworkLinkManager
+        extends AbstractVirtualListenerManager<LinkEvent, LinkListener>
+        implements LinkService {
+
+    private static final String DEVICE_NULL = "Device cannot be null";
+    private static final String CONNECT_POINT_NULL = "Connect point cannot be null";
+
+    /**
+     * Creates a new VirtualNetworkLinkService object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual networkIdentifier
+     */
+    public VirtualNetworkLinkManager(VirtualNetworkService virtualNetworkManager,
+                                     NetworkId networkId) {
+        super(virtualNetworkManager, networkId, LinkEvent.class);
+    }
+
+    @Override
+    public int getLinkCount() {
+        return manager.getVirtualLinks(this.networkId()).size();
+    }
+
+    @Override
+    public Iterable<Link> getLinks() {
+        return manager.getVirtualLinks(this.networkId())
+                .stream().collect(Collectors.toSet());
+    }
+
+    @Override
+    public Iterable<Link> getActiveLinks() {
+
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (link.state().equals(Link.State.ACTIVE)))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Link> getDeviceLinks(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (deviceId.equals(link.src().elementId()) ||
+                        deviceId.equals(link.dst().elementId())))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (deviceId.equals(link.dst().elementId())))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_NULL);
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (deviceId.equals(link.src().elementId())))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Link> getLinks(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECT_POINT_NULL);
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (connectPoint.equals(link.src()) ||
+                        connectPoint.equals(link.dst())))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Link> getEgressLinks(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECT_POINT_NULL);
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (connectPoint.equals(link.dst())))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Set<Link> getIngressLinks(ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECT_POINT_NULL);
+        return manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (connectPoint.equals(link.src())))
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public Link getLink(ConnectPoint src, ConnectPoint dst) {
+        checkNotNull(src, CONNECT_POINT_NULL);
+        checkNotNull(dst, CONNECT_POINT_NULL);
+        Optional<VirtualLink> foundLink =  manager.getVirtualLinks(this.networkId())
+                .stream()
+                .filter(link -> (src.equals(link.src()) &&
+                        dst.equals(link.dst())))
+                .findFirst();
+
+        if (foundLink.isPresent()) {
+            return foundLink.get();
+        }
+        return null;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
new file mode 100644
index 0000000..3b4ed8c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManager.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+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.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStoreDelegate;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.VnetService;
+import org.onosproject.incubator.net.virtual.event.VirtualEvent;
+import org.onosproject.incubator.net.virtual.event.VirtualListenerRegistryManager;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderService;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractListenerProviderRegistry;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.TopologyService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of the virtual network service.
+ */
+@Component(service = {
+                   VirtualNetworkService.class,
+                   VirtualNetworkAdminService.class,
+                   VirtualNetworkService.class,
+                   VirtualNetworkProviderRegistry.class
+            })
+public class VirtualNetworkManager
+        extends AbstractListenerProviderRegistry<VirtualNetworkEvent,
+        VirtualNetworkListener, VirtualNetworkProvider, VirtualNetworkProviderService>
+        implements VirtualNetworkService, VirtualNetworkAdminService, VirtualNetworkProviderRegistry {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String TENANT_NULL = "Tenant ID cannot be null";
+    private static final String NETWORK_NULL = "Network ID cannot be null";
+    private static final String DEVICE_NULL = "Device ID cannot be null";
+    private static final String LINK_POINT_NULL = "Link end-point cannot be null";
+
+    private static final String VIRTUAL_NETWORK_APP_ID_STRING =
+            "org.onosproject.virtual-network";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualNetworkStore store;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    private VirtualNetworkStoreDelegate delegate = this::post;
+
+    private ServiceDirectory serviceDirectory = new DefaultServiceDirectory();
+    private ApplicationId appId;
+
+    // TODO: figure out how to coordinate "implementation" of a virtual network in a cluster
+
+    /**
+     * Only used for Junit test methods outside of this package.
+     *
+     * @param store virtual network store
+     */
+    public void setStore(VirtualNetworkStore store) {
+        this.store = store;
+    }
+
+    @Activate
+    public void activate() {
+        eventDispatcher.addSink(VirtualNetworkEvent.class, listenerRegistry);
+        eventDispatcher.addSink(VirtualEvent.class,
+                                VirtualListenerRegistryManager.getInstance());
+        store.setDelegate(delegate);
+        appId = coreService.registerApplication(VIRTUAL_NETWORK_APP_ID_STRING);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        store.unsetDelegate(delegate);
+        eventDispatcher.removeSink(VirtualNetworkEvent.class);
+        eventDispatcher.removeSink(VirtualEvent.class);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void registerTenantId(TenantId tenantId) {
+        checkNotNull(tenantId, TENANT_NULL);
+        store.addTenantId(tenantId);
+    }
+
+    @Override
+    public void unregisterTenantId(TenantId tenantId) {
+        checkNotNull(tenantId, TENANT_NULL);
+        store.removeTenantId(tenantId);
+    }
+
+    @Override
+    public Set<TenantId> getTenantIds() {
+        return store.getTenantIds();
+    }
+
+    @Override
+    public VirtualNetwork createVirtualNetwork(TenantId tenantId) {
+        checkNotNull(tenantId, TENANT_NULL);
+        return store.addNetwork(tenantId);
+    }
+
+    @Override
+    public void removeVirtualNetwork(NetworkId networkId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        store.removeNetwork(networkId);
+    }
+
+    @Override
+    public VirtualDevice createVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(deviceId, DEVICE_NULL);
+        return store.addDevice(networkId, deviceId);
+    }
+
+    @Override
+    public void removeVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(deviceId, DEVICE_NULL);
+        store.removeDevice(networkId, deviceId);
+    }
+
+    @Override
+    public VirtualHost createVirtualHost(NetworkId networkId, HostId hostId,
+                                         MacAddress mac, VlanId vlan,
+                                         HostLocation location, Set<IpAddress> ips) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(hostId, DEVICE_NULL);
+        return store.addHost(networkId, hostId, mac, vlan, location, ips);
+    }
+
+    @Override
+    public void removeVirtualHost(NetworkId networkId, HostId hostId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(hostId, DEVICE_NULL);
+        store.removeHost(networkId, hostId);
+    }
+
+    @Override
+    public VirtualLink createVirtualLink(NetworkId networkId,
+                                         ConnectPoint src, ConnectPoint dst) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(src, LINK_POINT_NULL);
+        checkNotNull(dst, LINK_POINT_NULL);
+        ConnectPoint physicalSrc = mapVirtualToPhysicalPort(networkId, src);
+        checkNotNull(physicalSrc, LINK_POINT_NULL);
+        ConnectPoint physicalDst = mapVirtualToPhysicalPort(networkId, dst);
+        checkNotNull(physicalDst, LINK_POINT_NULL);
+
+        VirtualNetworkProvider provider = getProvider(DefaultVirtualLink.PID);
+        Link.State state = Link.State.INACTIVE;
+        if (provider != null) {
+            boolean traversable = provider.isTraversable(physicalSrc, physicalDst);
+            state = traversable ? Link.State.ACTIVE : Link.State.INACTIVE;
+        }
+        return store.addLink(networkId, src, dst, state, null);
+    }
+
+    /**
+     * Maps the virtual connect point to a physical connect point.
+     *
+     * @param networkId network identifier
+     * @param virtualCp virtual connect point
+     * @return physical connect point
+     */
+    private ConnectPoint mapVirtualToPhysicalPort(NetworkId networkId,
+                                                  ConnectPoint virtualCp) {
+        Set<VirtualPort> ports = store.getPorts(networkId, virtualCp.deviceId());
+        for (VirtualPort port : ports) {
+            if (port.number().equals(virtualCp.port())) {
+                return new ConnectPoint(port.realizedBy().deviceId(),
+                                        port.realizedBy().port());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Maps the physical connect point to a virtual connect point.
+     *
+     * @param networkId  network identifier
+     * @param physicalCp physical connect point
+     * @return virtual connect point
+     */
+    private ConnectPoint mapPhysicalToVirtualToPort(NetworkId networkId,
+                                                    ConnectPoint physicalCp) {
+        Set<VirtualPort> ports = store.getPorts(networkId, null);
+        for (VirtualPort port : ports) {
+            if (port.realizedBy().deviceId().equals(physicalCp.elementId()) &&
+                    port.realizedBy().port().equals(physicalCp.port())) {
+                return new ConnectPoint(port.element().id(), port.number());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void removeVirtualLink(NetworkId networkId, ConnectPoint src,
+                                  ConnectPoint dst) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(src, LINK_POINT_NULL);
+        checkNotNull(dst, LINK_POINT_NULL);
+        store.removeLink(networkId, src, dst);
+    }
+
+    @Override
+    public VirtualPort createVirtualPort(NetworkId networkId, DeviceId deviceId,
+                                         PortNumber portNumber, ConnectPoint realizedBy) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(deviceId, DEVICE_NULL);
+        checkNotNull(portNumber, "Port description cannot be null");
+        return store.addPort(networkId, deviceId, portNumber, realizedBy);
+    }
+
+    @Override
+    public void bindVirtualPort(NetworkId networkId, DeviceId deviceId,
+                PortNumber portNumber, ConnectPoint realizedBy) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(deviceId, DEVICE_NULL);
+        checkNotNull(portNumber, "Port description cannot be null");
+        checkNotNull(realizedBy, "Physical port description cannot be null");
+
+        store.bindPort(networkId, deviceId, portNumber, realizedBy);
+    }
+
+    @Override
+    public void updatePortState(NetworkId networkId, DeviceId deviceId,
+                PortNumber portNumber, boolean isEnabled) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(deviceId, DEVICE_NULL);
+        checkNotNull(portNumber, "Port description cannot be null");
+
+        store.updatePortState(networkId, deviceId, portNumber, isEnabled);
+    }
+
+    @Override
+    public void removeVirtualPort(NetworkId networkId, DeviceId deviceId,
+                                  PortNumber portNumber) {
+        checkNotNull(networkId, NETWORK_NULL);
+        checkNotNull(deviceId, DEVICE_NULL);
+        checkNotNull(portNumber, "Port number cannot be null");
+        store.removePort(networkId, deviceId, portNumber);
+    }
+
+    @Override
+    public ServiceDirectory getServiceDirectory() {
+        return serviceDirectory;
+    }
+
+    @Override
+    public Set<VirtualNetwork> getVirtualNetworks(TenantId tenantId) {
+        checkNotNull(tenantId, TENANT_NULL);
+        return store.getNetworks(tenantId);
+    }
+
+    @Override
+    public VirtualNetwork getVirtualNetwork(NetworkId networkId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        return store.getNetwork(networkId);
+    }
+
+    @Override
+    public TenantId getTenantId(NetworkId networkId) {
+        VirtualNetwork virtualNetwork = getVirtualNetwork(networkId);
+        checkNotNull(virtualNetwork, "The network does not exist.");
+        return virtualNetwork.tenantId();
+    }
+
+    @Override
+    public Set<VirtualDevice> getVirtualDevices(NetworkId networkId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        return store.getDevices(networkId);
+    }
+
+    @Override
+    public Set<VirtualHost> getVirtualHosts(NetworkId networkId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        return store.getHosts(networkId);
+    }
+
+    @Override
+    public Set<VirtualLink> getVirtualLinks(NetworkId networkId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        return store.getLinks(networkId);
+    }
+
+    @Override
+    public Set<VirtualPort> getVirtualPorts(NetworkId networkId, DeviceId deviceId) {
+        checkNotNull(networkId, NETWORK_NULL);
+        return store.getPorts(networkId, deviceId);
+    }
+
+    @Override
+    public Set<DeviceId> getPhysicalDevices(NetworkId networkId, DeviceId deviceId) {
+        checkNotNull(networkId, "Network ID cannot be null");
+        checkNotNull(deviceId, "Virtual device ID cannot be null");
+        Set<VirtualPort> virtualPortSet = getVirtualPorts(networkId, deviceId);
+        Set<DeviceId> physicalDeviceSet = new HashSet<>();
+
+        virtualPortSet.forEach(virtualPort -> {
+            if (virtualPort.realizedBy() != null) {
+                physicalDeviceSet.add(virtualPort.realizedBy().deviceId());
+            }
+        });
+
+        return ImmutableSet.copyOf(physicalDeviceSet);
+    }
+
+    private final Map<ServiceKey, VnetService> networkServices = Maps.newConcurrentMap();
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T get(NetworkId networkId, Class<T> serviceClass) {
+        checkNotNull(networkId, NETWORK_NULL);
+        ServiceKey serviceKey = networkServiceKey(networkId, serviceClass);
+        VnetService service = lookup(serviceKey);
+        if (service == null) {
+            service = create(serviceKey);
+        }
+        return (T) service;
+    }
+
+    @Override
+    public ApplicationId getVirtualNetworkApplicationId(NetworkId networkId) {
+        return appId;
+    }
+
+    /**
+     * Returns the Vnet service matching the service key.
+     *
+     * @param serviceKey service key
+     * @return vnet service
+     */
+    private VnetService lookup(ServiceKey serviceKey) {
+        return networkServices.get(serviceKey);
+    }
+
+    /**
+     * Creates a new service key using the specified network identifier and service class.
+     *
+     * @param networkId    network identifier
+     * @param serviceClass service class
+     * @param <T>          type of service
+     * @return service key
+     */
+    private <T> ServiceKey networkServiceKey(NetworkId networkId, Class<T> serviceClass) {
+        return new ServiceKey(networkId, serviceClass);
+    }
+
+
+    /**
+     * Create a new vnet service instance.
+     *
+     * @param serviceKey service key
+     * @return vnet service
+     */
+    private VnetService create(ServiceKey serviceKey) {
+        VirtualNetwork network = getVirtualNetwork(serviceKey.networkId());
+        checkNotNull(network, NETWORK_NULL);
+
+        VnetService service;
+        if (serviceKey.serviceClass.equals(DeviceService.class)) {
+            service = new VirtualNetworkDeviceManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(LinkService.class)) {
+            service = new VirtualNetworkLinkManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(TopologyService.class)) {
+            service = new VirtualNetworkTopologyManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(IntentService.class)) {
+            service = new VirtualNetworkIntentManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(HostService.class)) {
+            service = new VirtualNetworkHostManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(PathService.class)) {
+            service = new VirtualNetworkPathManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(FlowRuleService.class)) {
+            service = new VirtualNetworkFlowRuleManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(PacketService.class)) {
+            service = new VirtualNetworkPacketManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(GroupService.class)) {
+            service = new VirtualNetworkGroupManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(MeterService.class)) {
+            service = new VirtualNetworkMeterManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(FlowObjectiveService.class)) {
+            service = new VirtualNetworkFlowObjectiveManager(this, network.id());
+        } else if (serviceKey.serviceClass.equals(MastershipService.class) ||
+                serviceKey.serviceClass.equals(MastershipAdminService.class) ||
+                serviceKey.serviceClass.equals(MastershipTermService.class)) {
+            service = new VirtualNetworkMastershipManager(this, network.id());
+        } else {
+            return null;
+        }
+        networkServices.put(serviceKey, service);
+        return service;
+    }
+
+    /**
+     * Service key class.
+     */
+    private static class ServiceKey {
+        final NetworkId networkId;
+        final Class serviceClass;
+
+        /**
+         * Constructor for service key.
+         *
+         * @param networkId    network identifier
+         * @param serviceClass service class
+         */
+        ServiceKey(NetworkId networkId, Class serviceClass) {
+
+            checkNotNull(networkId, NETWORK_NULL);
+            this.networkId = networkId;
+            this.serviceClass = serviceClass;
+        }
+
+        /**
+         * Returns the network identifier.
+         *
+         * @return network identifier
+         */
+        public NetworkId networkId() {
+            return networkId;
+        }
+
+        /**
+         * Returns the service class.
+         *
+         * @return service class
+         */
+        public Class serviceClass() {
+            return serviceClass;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(networkId, serviceClass);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof ServiceKey) {
+                ServiceKey that = (ServiceKey) obj;
+                return Objects.equals(this.networkId, that.networkId) &&
+                        Objects.equals(this.serviceClass, that.serviceClass);
+            }
+            return false;
+        }
+    }
+
+    @Override
+    protected VirtualNetworkProviderService
+    createProviderService(VirtualNetworkProvider provider) {
+        return new InternalVirtualNetworkProviderService(provider);
+    }
+
+    /**
+     * Service issued to registered virtual network providers so that they
+     * can interact with the core.
+     */
+    private class InternalVirtualNetworkProviderService
+            extends AbstractProviderService<VirtualNetworkProvider>
+            implements VirtualNetworkProviderService {
+        /**
+         * Constructor.
+         * @param provider virtual network provider
+         */
+        InternalVirtualNetworkProviderService(VirtualNetworkProvider provider) {
+            super(provider);
+        }
+
+        @Override
+        public void topologyChanged(Set<Set<ConnectPoint>> clusters) {
+            Set<TenantId> tenantIds = getTenantIds();
+            tenantIds.forEach(tenantId -> {
+                Set<VirtualNetwork> virtualNetworks = getVirtualNetworks(tenantId);
+
+                virtualNetworks.forEach(virtualNetwork -> {
+                    Set<VirtualLink> virtualLinks = getVirtualLinks(virtualNetwork.id());
+
+                    virtualLinks.forEach(virtualLink -> {
+                        if (isVirtualLinkInCluster(virtualNetwork.id(),
+                                                   virtualLink, clusters)) {
+                            store.updateLink(virtualLink, virtualLink.tunnelId(),
+                                             Link.State.ACTIVE);
+                        } else {
+                            store.updateLink(virtualLink, virtualLink.tunnelId(),
+                                             Link.State.INACTIVE);
+                        }
+                    });
+                });
+            });
+        }
+
+        /**
+         * Determines if the virtual link (both source and destination connect point)
+         * is in a cluster.
+         *
+         * @param networkId   virtual network identifier
+         * @param virtualLink virtual link
+         * @param clusters    topology clusters
+         * @return true if the virtual link is in a cluster.
+         */
+        private boolean isVirtualLinkInCluster(NetworkId networkId, VirtualLink virtualLink,
+                                               Set<Set<ConnectPoint>> clusters) {
+            ConnectPoint srcPhysicalCp =
+                    mapVirtualToPhysicalPort(networkId, virtualLink.src());
+            ConnectPoint dstPhysicalCp =
+                    mapVirtualToPhysicalPort(networkId, virtualLink.dst());
+
+            final boolean[] foundSrc = {false};
+            final boolean[] foundDst = {false};
+            clusters.forEach(connectPoints -> {
+                connectPoints.forEach(connectPoint -> {
+                    if (connectPoint.equals(srcPhysicalCp)) {
+                        foundSrc[0] = true;
+                    } else if (connectPoint.equals(dstPhysicalCp)) {
+                        foundDst[0] = true;
+                    }
+                });
+                if (foundSrc[0] && foundDst[0]) {
+                    return;
+                }
+            });
+            return foundSrc[0] && foundDst[0];
+        }
+
+        @Override
+        public void tunnelUp(NetworkId networkId, ConnectPoint src,
+                             ConnectPoint dst, TunnelId tunnelId) {
+            ConnectPoint srcVirtualCp = mapPhysicalToVirtualToPort(networkId, src);
+            ConnectPoint dstVirtualCp = mapPhysicalToVirtualToPort(networkId, dst);
+            if ((srcVirtualCp == null) || (dstVirtualCp == null)) {
+                log.error("Src or dst virtual connection point was not found.");
+            }
+
+            VirtualLink virtualLink = store.getLink(networkId, srcVirtualCp, dstVirtualCp);
+            if (virtualLink != null) {
+                store.updateLink(virtualLink, tunnelId, Link.State.ACTIVE);
+            }
+        }
+
+        @Override
+        public void tunnelDown(NetworkId networkId, ConnectPoint src,
+                               ConnectPoint dst, TunnelId tunnelId) {
+            ConnectPoint srcVirtualCp = mapPhysicalToVirtualToPort(networkId, src);
+            ConnectPoint dstVirtualCp = mapPhysicalToVirtualToPort(networkId, dst);
+            if ((srcVirtualCp == null) || (dstVirtualCp == null)) {
+                log.error("Src or dst virtual connection point was not found.");
+            }
+
+            VirtualLink virtualLink = store.getLink(networkId, srcVirtualCp, dstVirtualCp);
+            if (virtualLink != null) {
+                store.updateLink(virtualLink, tunnelId, Link.State.INACTIVE);
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
new file mode 100644
index 0000000..57c0774
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManager.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import org.onlab.metrics.MetricsService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.MetricsHelper;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.slf4j.Logger;
+import com.codahale.metrics.Timer;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.metrics.MetricsUtil.startTimer;
+import static org.onlab.metrics.MetricsUtil.stopTimer;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class VirtualNetworkMastershipManager
+        extends AbstractVirtualListenerManager<MastershipEvent, MastershipListener>
+        implements MastershipService, MastershipAdminService, MastershipTermService,
+        MetricsHelper {
+
+    private static final String NODE_ID_NULL = "Node ID cannot be null";
+    private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+    private static final String ROLE_NULL = "Mastership role cannot be null";
+
+    private final Logger log = getLogger(getClass());
+
+    protected ClusterService clusterService;
+
+    VirtualNetworkMastershipStore store;
+    MastershipStoreDelegate storeDelegate;
+
+    private NodeId localNodeId;
+    private Timer requestRoleTimer;
+
+    /**
+     * Creates a new VirtualNetworkMastershipManager object.
+     *
+     * @param manager virtual network manager service
+     * @param networkId virtual network identifier
+     */
+    public VirtualNetworkMastershipManager(VirtualNetworkService manager, NetworkId networkId) {
+        super(manager, networkId, MastershipEvent.class);
+
+        clusterService = serviceDirectory.get(ClusterService.class);
+
+        store = serviceDirectory.get(VirtualNetworkMastershipStore.class);
+        this.storeDelegate = new InternalDelegate();
+        store.setDelegate(networkId, this.storeDelegate);
+
+        requestRoleTimer = createTimer("Virtual-mastership", "requestRole", "responseTime");
+        localNodeId = clusterService.getLocalNode().id();
+    }
+
+    @Override
+    public CompletableFuture<Void> setRole(NodeId nodeId, DeviceId deviceId,
+                                           MastershipRole role) {
+        checkNotNull(nodeId, NODE_ID_NULL);
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        checkNotNull(role, ROLE_NULL);
+
+        CompletableFuture<MastershipEvent> eventFuture = null;
+
+        switch (role) {
+            case MASTER:
+                eventFuture = store.setMaster(networkId, nodeId, deviceId);
+                break;
+            case STANDBY:
+                eventFuture = store.setStandby(networkId, nodeId, deviceId);
+                break;
+            case NONE:
+                eventFuture = store.relinquishRole(networkId, nodeId, deviceId);
+                break;
+            default:
+                log.info("Unknown role; ignoring");
+                return CompletableFuture.completedFuture(null);
+        }
+
+        return eventFuture.thenAccept(this::post).thenApply(v -> null);
+    }
+
+    @Override
+    public MastershipRole getLocalRole(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+
+        return store.getRole(networkId, localNodeId, deviceId);
+    }
+
+    @Override
+    public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+
+        final Timer.Context timer = startTimer(requestRoleTimer);
+        return store.requestRole(networkId, deviceId)
+                .whenComplete((result, error) -> stopTimer(timer));
+    }
+
+    @Override
+    public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) {
+        return store.relinquishRole(networkId, localNodeId, deviceId)
+                .thenAccept(this::post)
+                .thenApply(v -> null);
+    }
+
+    @Override
+    public NodeId getMasterFor(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+
+        return store.getMaster(networkId, deviceId);
+    }
+
+    @Override
+    public RoleInfo getNodesFor(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+
+        return store.getNodes(networkId, deviceId);
+    }
+
+    @Override
+    public MastershipInfo getMastershipFor(DeviceId deviceId) {
+        checkNotNull(deviceId, DEVICE_ID_NULL);
+        return store.getMastership(networkId, deviceId);
+    }
+
+    @Override
+    public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+        checkNotNull(nodeId, NODE_ID_NULL);
+
+        return store.getDevices(networkId, nodeId);
+    }
+
+    @Override
+    public MastershipTerm getMastershipTerm(DeviceId deviceId) {
+        return store.getTermFor(networkId, deviceId);
+    }
+
+    @Override
+    public MetricsService metricsService() {
+        //TODO: support metric service for virtual network
+        log.warn("Currently, virtual network does not support metric service.");
+        return null;
+    }
+
+    @Override
+    public void balanceRoles() {
+        //FIXME: More advanced logic for balancing virtual network roles.
+        List<ControllerNode> nodes = clusterService.getNodes().stream()
+                .filter(n -> clusterService.getState(n.id())
+                        .equals(ControllerNode.State.ACTIVE))
+                .collect(Collectors.toList());
+
+        nodes.sort(Comparator.comparing(ControllerNode::id));
+
+        //Pick a node using network Id,
+        NodeId masterNode = nodes.get((int) ((networkId.id() - 1) % nodes.size())).id();
+
+        List<CompletableFuture<Void>> setRoleFutures = Lists.newLinkedList();
+        for (VirtualDevice device : manager.getVirtualDevices(networkId)) {
+            setRoleFutures.add(setRole(masterNode, device.id(), MastershipRole.MASTER));
+        }
+
+        CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf(
+                setRoleFutures.toArray(new CompletableFuture[setRoleFutures.size()]));
+
+        Futures.getUnchecked(balanceRolesFuture);
+    }
+
+    public class InternalDelegate implements MastershipStoreDelegate {
+        @Override
+        public void notify(MastershipEvent event) {
+            post(event);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManager.java
new file mode 100644
index 0000000..2cce14c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManager.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.util.TriConsumer;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMeterStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterFeatures;
+import org.onosproject.net.meter.MeterFeaturesKey;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterListener;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.meter.MeterState;
+import org.onosproject.net.meter.MeterStoreDelegate;
+import org.onosproject.net.meter.MeterStoreResult;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class VirtualNetworkMeterManager
+        extends AbstractVirtualListenerManager<MeterEvent, MeterListener>
+        implements MeterService {
+
+    private static final String METERCOUNTERIDENTIFIER = "meter-id-counter-%s";
+    private final Logger log = getLogger(getClass());
+
+    protected StorageService coreStorageService;
+
+    protected VirtualNetworkMeterStore store;
+    private final MeterStoreDelegate storeDelegate = new InternalMeterStoreDelegate();
+
+    private VirtualProviderRegistryService providerRegistryService;
+    private InternalMeterProviderService innerProviderService;
+
+    private Map<DeviceId, AtomicCounter> meterIdCounters
+            = Maps.newConcurrentMap();
+
+    private TriConsumer<MeterRequest, MeterStoreResult, Throwable> onComplete;
+
+    /**
+     * Creates a new VirtualNetworkMeterManager object.
+     *
+     * @param manager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+    public VirtualNetworkMeterManager(VirtualNetworkService manager,
+                                      NetworkId networkId) {
+        super(manager, networkId, MeterEvent.class);
+
+        coreStorageService = serviceDirectory.get(StorageService.class);
+        providerRegistryService =
+                serviceDirectory.get(VirtualProviderRegistryService.class);
+
+        store = serviceDirectory.get(VirtualNetworkMeterStore.class);
+        store.setDelegate(networkId, this.storeDelegate);
+
+        innerProviderService = new InternalMeterProviderService();
+        providerRegistryService.registerProviderService(networkId(), innerProviderService);
+
+
+        onComplete = (request, result, error) -> {
+            request.context().ifPresent(c -> {
+                if (error != null) {
+                    c.onError(request, MeterFailReason.UNKNOWN);
+                } else {
+                    if (result.reason().isPresent()) {
+                        c.onError(request, result.reason().get());
+                    } else {
+                        c.onSuccess(request);
+                    }
+                }
+            });
+
+        };
+
+        log.info("Started");
+    }
+
+    @Override
+    public Meter submit(MeterRequest request) {
+
+        MeterId id = allocateMeterId(request.deviceId());
+
+        Meter.Builder mBuilder = DefaultMeter.builder()
+                .forDevice(request.deviceId())
+                .fromApp(request.appId())
+                .withBands(request.bands())
+                .withId(id)
+                .withUnit(request.unit());
+
+        if (request.isBurst()) {
+            mBuilder.burst();
+        }
+        DefaultMeter m = (DefaultMeter) mBuilder.build();
+        m.setState(MeterState.PENDING_ADD);
+        store.storeMeter(networkId(), m).whenComplete((result, error) ->
+                                                 onComplete.accept(request, result, error));
+        return m;
+    }
+
+    @Override
+    public void withdraw(MeterRequest request, MeterId meterId) {
+        Meter.Builder mBuilder = DefaultMeter.builder()
+                .forDevice(request.deviceId())
+                .fromApp(request.appId())
+                .withBands(request.bands())
+                .withId(meterId)
+                .withUnit(request.unit());
+
+        if (request.isBurst()) {
+            mBuilder.burst();
+        }
+
+        DefaultMeter m = (DefaultMeter) mBuilder.build();
+        m.setState(MeterState.PENDING_REMOVE);
+        store.deleteMeter(networkId(), m).whenComplete((result, error) ->
+                                                  onComplete.accept(request, result, error));
+    }
+
+    @Override
+    public Meter getMeter(DeviceId deviceId, MeterId id) {
+        MeterKey key = MeterKey.key(deviceId, id);
+        return store.getMeter(networkId(), key);
+    }
+
+    @Override
+    public Collection<Meter> getMeters(DeviceId deviceId) {
+        return store.getAllMeters(networkId()).stream()
+                .filter(m -> m.deviceId().equals(deviceId)).collect(Collectors.toList());
+    }
+
+    @Override
+    public Collection<Meter> getAllMeters() {
+        return store.getAllMeters(networkId());
+    }
+
+    private long queryMeters(DeviceId device) {
+        //FIXME: how to decide maximum number of meters per virtual device?
+        return 1;
+    }
+
+    private AtomicCounter allocateCounter(DeviceId deviceId) {
+        return coreStorageService
+                .getAtomicCounter(String.format(METERCOUNTERIDENTIFIER, deviceId));
+    }
+
+    public MeterId allocateMeterId(DeviceId deviceId) {
+        long maxMeters = store.getMaxMeters(networkId(), MeterFeaturesKey.key(deviceId));
+        if (maxMeters == 0L) {
+            // MeterFeatures couldn't be retrieved, trying with queryMeters
+            maxMeters = queryMeters(deviceId);
+        }
+
+        if (maxMeters == 0L) {
+            throw new IllegalStateException("Meters not supported by device " + deviceId);
+        }
+
+        final long mmeters = maxMeters;
+        long id = meterIdCounters.compute(deviceId, (k, v) -> {
+            if (v == null) {
+                return allocateCounter(k);
+            }
+            if (v.get() >= mmeters) {
+                throw new IllegalStateException("Maximum number of meters " +
+                                                        meterIdCounters.get(deviceId).get() +
+                                                        " reached for device " + deviceId +
+                                                        " virtual network " + networkId());
+            }
+            return v;
+        }).incrementAndGet();
+
+        return MeterId.meterId(id);
+    }
+
+    @Override
+    public void freeMeterId(DeviceId deviceId, MeterId meterId) {
+        // Do nothing
+    }
+
+    private class InternalMeterProviderService
+            extends AbstractVirtualProviderService<VirtualMeterProvider>
+            implements VirtualMeterProviderService {
+
+        /**
+         * Creates a provider service on behalf of the specified provider.
+         */
+        protected InternalMeterProviderService() {
+            Set<ProviderId> providerIds =
+                    providerRegistryService.getProvidersByService(this);
+            ProviderId providerId = providerIds.stream().findFirst().get();
+            VirtualMeterProvider provider = (VirtualMeterProvider)
+                    providerRegistryService.getProvider(providerId);
+            setProvider(provider);
+        }
+
+        @Override
+        public void meterOperationFailed(MeterOperation operation,
+                                         MeterFailReason reason) {
+            store.failedMeter(networkId(), operation, reason);
+        }
+
+        @Override
+        public void pushMeterMetrics(DeviceId deviceId, Collection<Meter> meterEntries) {
+            //FIXME: FOLLOWING CODE CANNOT BE TESTED UNTIL SOMETHING THAT
+            //FIXME: IMPLEMENTS METERS EXISTS
+            Map<Pair<DeviceId, MeterId>, Meter> storedMeterMap =
+                    store.getAllMeters(networkId()).stream()
+                    .collect(Collectors.toMap(m -> Pair.of(m.deviceId(), m.id()), Function.identity()));
+
+            meterEntries.stream()
+                    .filter(m -> storedMeterMap.remove(Pair.of(m.deviceId(), m.id())) != null)
+                    .forEach(m -> store.updateMeterState(networkId(), m));
+
+            storedMeterMap.values().forEach(m -> {
+                if (m.state() == MeterState.PENDING_ADD) {
+                    provider().performMeterOperation(networkId(), m.deviceId(),
+                                                     new MeterOperation(m,
+                                                                        MeterOperation.Type.MODIFY));
+                } else if (m.state() == MeterState.PENDING_REMOVE) {
+                    store.deleteMeterNow(networkId(), m);
+                }
+            });
+        }
+
+        @Override
+        public void pushMeterFeatures(DeviceId deviceId, MeterFeatures meterfeatures) {
+            store.storeMeterFeatures(networkId(), meterfeatures);
+        }
+
+        @Override
+        public void deleteMeterFeatures(DeviceId deviceId) {
+            store.deleteMeterFeatures(networkId(), deviceId);
+        }
+    }
+
+    private class InternalMeterStoreDelegate implements MeterStoreDelegate {
+
+        @Override
+        public void notify(MeterEvent event) {
+            DeviceId deviceId = event.subject().deviceId();
+            VirtualMeterProvider p = innerProviderService.provider();
+
+            switch (event.type()) {
+                case METER_ADD_REQ:
+                    p.performMeterOperation(networkId(), deviceId,
+                                            new MeterOperation(event.subject(),
+                                                               MeterOperation.Type.ADD));
+                    break;
+                case METER_REM_REQ:
+                    p.performMeterOperation(networkId(), deviceId,
+                                            new MeterOperation(event.subject(),
+                                                               MeterOperation.Type.REMOVE));
+                    break;
+                default:
+                    log.warn("Unknown meter event {}", event.type());
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManager.java
new file mode 100644
index 0000000..d1869ce
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManager.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.virtual.AbstractVnetService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.packet.DefaultPacketRequest;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketProcessorEntry;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public class VirtualNetworkPacketManager extends AbstractVnetService
+        implements PacketService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final VirtualNetworkService manager;
+
+    protected VirtualNetworkPacketStore store;
+    private final List<ProcessorEntry> processors = Lists.newCopyOnWriteArrayList();
+
+    private NodeId localNodeId;
+
+    private DeviceService deviceService;
+    private FlowObjectiveService objectiveService;
+
+    private VirtualProviderRegistryService providerRegistryService = null;
+
+    private InternalPacketProviderService providerService = null;
+
+    public VirtualNetworkPacketManager(VirtualNetworkService virtualNetworkManager,
+                                       NetworkId networkId) {
+        super(virtualNetworkManager, networkId);
+        this.manager = virtualNetworkManager;
+
+        //Set node id as same as the node hosting virtual manager
+        ClusterService clusterService = serviceDirectory.get(ClusterService.class);
+        this.localNodeId = clusterService.getLocalNode().id();
+
+        this.store = serviceDirectory.get(VirtualNetworkPacketStore.class);
+        this.store.setDelegate(networkId(), new InternalStoreDelegate());
+
+        this.deviceService = manager.get(networkId(), DeviceService.class);
+        this.objectiveService = manager.get(networkId(), FlowObjectiveService.class);
+
+        providerRegistryService =
+                serviceDirectory.get(VirtualProviderRegistryService.class);
+        providerService = new InternalPacketProviderService();
+        providerRegistryService.registerProviderService(networkId(), providerService);
+    }
+
+    @Override
+    public void addProcessor(PacketProcessor processor, int priority) {
+        ProcessorEntry entry = new ProcessorEntry(processor, priority);
+
+        // Insert the new processor according to its priority.
+        int i = 0;
+        for (; i < processors.size(); i++) {
+            if (priority < processors.get(i).priority()) {
+                break;
+            }
+        }
+        processors.add(i, entry);
+    }
+
+    @Override
+    public void removeProcessor(PacketProcessor processor) {
+        // Remove the processor entry.
+        for (int i = 0; i < processors.size(); i++) {
+            if (processors.get(i).processor() == processor) {
+                processors.remove(i);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public List<PacketProcessorEntry> getProcessors() {
+        return ImmutableList.copyOf(processors);
+    }
+
+    @Override
+    public void requestPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
+        PacketRequest request = new DefaultPacketRequest(selector, priority, appId,
+                                                         localNodeId, Optional.empty());
+        store.requestPackets(networkId(), request);
+    }
+
+    @Override
+    public void requestPackets(TrafficSelector selector, PacketPriority priority,
+                               ApplicationId appId, Optional<DeviceId> deviceId) {
+        PacketRequest request =
+                new DefaultPacketRequest(selector, priority, appId,
+                                         localNodeId, deviceId);
+
+        store.requestPackets(networkId(), request);
+    }
+
+    @Override
+    public void cancelPackets(TrafficSelector selector, PacketPriority priority, ApplicationId appId) {
+        PacketRequest request = new DefaultPacketRequest(selector, priority, appId,
+                                                         localNodeId, Optional.empty());
+        store.cancelPackets(networkId(), request);
+    }
+
+    @Override
+    public void cancelPackets(TrafficSelector selector, PacketPriority priority,
+                              ApplicationId appId, Optional<DeviceId> deviceId) {
+        PacketRequest request = new DefaultPacketRequest(selector, priority,
+                                                         appId, localNodeId,
+                                                         deviceId);
+        store.cancelPackets(networkId(), request);
+    }
+
+    @Override
+    public List<PacketRequest> getRequests() {
+        return store.existingRequests(networkId());
+    }
+
+    @Override
+    public void emit(OutboundPacket packet) {
+        store.emit(networkId(), packet);
+    }
+
+    /**
+     * Personalized packet provider service issued to the supplied provider.
+     */
+    private class InternalPacketProviderService
+            extends AbstractVirtualProviderService<VirtualPacketProvider>
+            implements VirtualPacketProviderService {
+
+        protected InternalPacketProviderService() {
+            super();
+
+            Set<ProviderId> providerIds =
+                    providerRegistryService.getProvidersByService(this);
+            ProviderId providerId = providerIds.stream().findFirst().get();
+            VirtualPacketProvider provider = (VirtualPacketProvider)
+                    providerRegistryService.getProvider(providerId);
+            setProvider(provider);
+        }
+
+        @Override
+        public void processPacket(PacketContext context) {
+            // TODO filter packets sent to processors based on registrations
+            for (ProcessorEntry entry : processors) {
+                try {
+                    long start = System.nanoTime();
+                    entry.processor().process(context);
+                    entry.addNanos(System.nanoTime() - start);
+                } catch (Exception e) {
+                    log.warn("Packet processor {} threw an exception", entry.processor(), e);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Entity for tracking stats for a packet processor.
+     */
+    private class ProcessorEntry implements PacketProcessorEntry {
+        private final PacketProcessor processor;
+        private final int priority;
+        private long invocations = 0;
+        private long nanos = 0;
+
+        public ProcessorEntry(PacketProcessor processor, int priority) {
+            this.processor = processor;
+            this.priority = priority;
+        }
+
+        @Override
+        public PacketProcessor processor() {
+            return processor;
+        }
+
+        @Override
+        public int priority() {
+            return priority;
+        }
+
+        @Override
+        public long invocations() {
+            return invocations;
+        }
+
+        @Override
+        public long totalNanos() {
+            return nanos;
+        }
+
+        @Override
+        public long averageNanos() {
+            return invocations > 0 ? nanos / invocations : 0;
+        }
+
+        void addNanos(long nanos) {
+            this.nanos += nanos;
+            this.invocations++;
+        }
+    }
+
+    private void localEmit(NetworkId networkId, OutboundPacket packet) {
+        Device device = deviceService.getDevice(packet.sendThrough());
+        if (device == null) {
+            return;
+        }
+        VirtualPacketProvider packetProvider = providerService.provider();
+
+        if (packetProvider != null) {
+            packetProvider.emit(networkId, packet);
+        }
+    }
+
+    /**
+     * Internal callback from the packet store.
+     */
+    protected class InternalStoreDelegate implements PacketStoreDelegate {
+        @Override
+        public void notify(PacketEvent event) {
+            localEmit(networkId(), event.subject());
+        }
+
+        @Override
+        public void requestPackets(PacketRequest request) {
+            DeviceId deviceid = request.deviceId().orElse(null);
+
+            if (deviceid != null) {
+                pushRule(deviceService.getDevice(deviceid), request);
+            } else {
+                pushToAllDevices(request);
+            }
+        }
+
+        @Override
+        public void cancelPackets(PacketRequest request) {
+            DeviceId deviceid = request.deviceId().orElse(null);
+
+            if (deviceid != null) {
+                removeRule(deviceService.getDevice(deviceid), request);
+            } else {
+                removeFromAllDevices(request);
+            }
+        }
+    }
+
+    /**
+     * Pushes packet intercept flow rules to the device.
+     *
+     * @param device  the device to push the rules to
+     * @param request the packet request
+     */
+    private void pushRule(Device device, PacketRequest request) {
+        if (!device.type().equals(Device.Type.VIRTUAL)) {
+            return;
+        }
+
+        ForwardingObjective forwarding = createBuilder(request)
+                .add(new ObjectiveContext() {
+                    @Override
+                    public void onError(Objective objective, ObjectiveError error) {
+                        log.warn("Failed to install packet request {} to {}: {}",
+                                 request, device.id(), error);
+                    }
+                });
+
+        objectiveService.forward(device.id(), forwarding);
+    }
+
+    /**
+     * Removes packet intercept flow rules from the device.
+     *
+     * @param device  the device to remove the rules deom
+     * @param request the packet request
+     */
+    private void removeRule(Device device, PacketRequest request) {
+        if (!device.type().equals(Device.Type.VIRTUAL)) {
+            return;
+        }
+        ForwardingObjective forwarding = createBuilder(request)
+                .remove(new ObjectiveContext() {
+                    @Override
+                    public void onError(Objective objective, ObjectiveError error) {
+                        log.warn("Failed to withdraw packet request {} from {}: {}",
+                                 request, device.id(), error);
+                    }
+                });
+        objectiveService.forward(device.id(), forwarding);
+    }
+
+    /**
+     * Pushes a packet request flow rule to all devices.
+     *
+     * @param request the packet request
+     */
+    private void pushToAllDevices(PacketRequest request) {
+        log.debug("Pushing packet request {} to all devices", request);
+        for (Device device : deviceService.getDevices()) {
+            pushRule(device, request);
+        }
+    }
+
+    /**
+     * Removes packet request flow rule from all devices.
+     *
+     * @param request the packet request
+     */
+    private void removeFromAllDevices(PacketRequest request) {
+        deviceService.getAvailableDevices().forEach(d -> removeRule(d, request));
+    }
+
+    private DefaultForwardingObjective.Builder createBuilder(PacketRequest request) {
+        return DefaultForwardingObjective.builder()
+                .withPriority(request.priority().priorityValue())
+                .withSelector(request.selector())
+                .fromApp(request.appId())
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withTreatment(DefaultTrafficTreatment.builder().punt().build())
+                .makePermanent();
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManager.java
new file mode 100644
index 0000000..de0dffa
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManager.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VnetService;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.AbstractPathService;
+import org.onosproject.net.topology.TopologyService;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Path service implementation built on the virtual network service.
+ */
+public class VirtualNetworkPathManager
+        extends AbstractPathService
+        implements PathService, VnetService {
+
+    private final NetworkId networkId;
+
+    /**
+     * Creates a new virtual network path service object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+
+    public VirtualNetworkPathManager(VirtualNetworkService virtualNetworkManager,
+                                     NetworkId networkId) {
+        this.networkId = networkId;
+
+        topologyService = virtualNetworkManager.get(networkId(), TopologyService.class);
+        hostService = virtualNetworkManager.get(networkId(), HostService.class);
+    }
+
+    @Override
+    public Set<Path> getPaths(ElementId src, ElementId dst) {
+        return super.getPaths(src, dst, (LinkWeigher) null);
+    }
+
+    @Override
+    public Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst) {
+        return getDisjointPaths(src, dst, (LinkWeigher) null);
+    }
+
+    @Override
+    public Set<DisjointPath> getDisjointPaths(ElementId src, ElementId dst,
+                                              Map<Link, Object> riskProfile) {
+        return getDisjointPaths(src, dst, (LinkWeigher) null, riskProfile);
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return this.networkId;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManager.java
new file mode 100644
index 0000000..aad3ac4
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManager.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onosproject.common.DefaultTopology;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.topology.ClusterId;
+import org.onosproject.net.topology.DefaultGraphDescription;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyGraph;
+import org.onosproject.net.topology.TopologyListener;
+import org.onosproject.net.topology.TopologyService;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.virtual.DefaultVirtualLink.PID;
+
+/**
+ * Topology service implementation built on the virtual network service.
+ */
+public class VirtualNetworkTopologyManager
+        extends AbstractVirtualListenerManager<TopologyEvent, TopologyListener>
+        implements TopologyService {
+
+    private static final String TOPOLOGY_NULL = "Topology cannot be null";
+    private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+    private static final String CLUSTER_ID_NULL = "Cluster ID cannot be null";
+    private static final String CLUSTER_NULL = "Topology cluster cannot be null";
+    private static final String CONNECTION_POINT_NULL = "Connection point cannot be null";
+    private static final String LINK_WEIGHT_NULL = "Link weight cannot be null";
+
+    /**
+     * Creates a new VirtualNetworkTopologyService object.
+     *
+     * @param virtualNetworkManager virtual network manager service
+     * @param networkId a virtual network identifier
+     */
+    public VirtualNetworkTopologyManager(VirtualNetworkService virtualNetworkManager,
+                                         NetworkId networkId) {
+        super(virtualNetworkManager, networkId, TopologyEvent.class);
+    }
+
+    @Override
+    public Topology currentTopology() {
+        Iterable<Device> devices = manager.getVirtualDevices(networkId())
+                .stream()
+                .collect(Collectors.toSet());
+        Iterable<Link> links = manager.getVirtualLinks(networkId())
+                .stream()
+                .collect(Collectors.toSet());
+
+        DefaultGraphDescription graph =
+                new DefaultGraphDescription(System.nanoTime(),
+                                            System.currentTimeMillis(),
+                                            devices, links);
+        return new DefaultTopology(PID, graph);
+    }
+
+    @Override
+    public boolean isLatest(Topology topology) {
+        Topology currentTopology = currentTopology();
+        return defaultTopology(topology).getGraph()
+                .equals(defaultTopology(currentTopology).getGraph());
+    }
+
+    @Override
+    public TopologyGraph getGraph(Topology topology) {
+        return defaultTopology(topology).getGraph();
+    }
+
+    // Validates the specified topology and returns it as a default
+    private DefaultTopology defaultTopology(Topology topology) {
+        checkNotNull(topology, TOPOLOGY_NULL);
+        checkArgument(topology instanceof DefaultTopology,
+                      "Topology class %s not supported", topology.getClass());
+        return (DefaultTopology) topology;
+    }
+
+    @Override
+    public Set<TopologyCluster> getClusters(Topology topology) {
+        return defaultTopology(topology).getClusters();
+    }
+
+    @Override
+    public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
+        checkNotNull(clusterId, CLUSTER_ID_NULL);
+        return defaultTopology(topology).getCluster(clusterId);
+    }
+
+    @Override
+    public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
+        checkNotNull(cluster, CLUSTER_NULL);
+        return defaultTopology(topology).getClusterDevices(cluster);
+    }
+
+    @Override
+    public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
+        checkNotNull(cluster, CLUSTER_NULL);
+        return defaultTopology(topology).getClusterLinks(cluster);
+    }
+
+    @Override
+    public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+        checkNotNull(src, DEVICE_ID_NULL);
+        checkNotNull(dst, DEVICE_ID_NULL);
+        return defaultTopology(topology).getPaths(src, dst);
+    }
+
+    @Override
+    public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+                              LinkWeigher weigher) {
+        checkNotNull(src, DEVICE_ID_NULL);
+        checkNotNull(dst, DEVICE_ID_NULL);
+        checkNotNull(weigher, LINK_WEIGHT_NULL);
+        return defaultTopology(topology).getPaths(src, dst, weigher);
+    }
+
+    @Override
+    public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+                                              DeviceId dst) {
+        checkNotNull(src, DEVICE_ID_NULL);
+        checkNotNull(dst, DEVICE_ID_NULL);
+        return defaultTopology(topology).getDisjointPaths(src, dst);
+    }
+
+    @Override
+    public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+                                              DeviceId dst,
+                                              LinkWeigher weigher) {
+        checkNotNull(src, DEVICE_ID_NULL);
+        checkNotNull(dst, DEVICE_ID_NULL);
+        checkNotNull(weigher, LINK_WEIGHT_NULL);
+        return defaultTopology(topology).getDisjointPaths(src, dst, weigher);
+    }
+
+    @Override
+    public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+                                              DeviceId dst,
+                                              Map<Link, Object> riskProfile) {
+        checkNotNull(src, DEVICE_ID_NULL);
+        checkNotNull(dst, DEVICE_ID_NULL);
+        return defaultTopology(topology).getDisjointPaths(src, dst, riskProfile);
+    }
+
+    @Override
+    public Set<DisjointPath> getDisjointPaths(Topology topology, DeviceId src,
+                                              DeviceId dst,
+                                              LinkWeigher weigher,
+                                              Map<Link, Object> riskProfile) {
+        checkNotNull(src, DEVICE_ID_NULL);
+        checkNotNull(dst, DEVICE_ID_NULL);
+        checkNotNull(weigher, LINK_WEIGHT_NULL);
+        return defaultTopology(topology).getDisjointPaths(src, dst, weigher,
+                riskProfile);
+    }
+
+    @Override
+    public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECTION_POINT_NULL);
+        return defaultTopology(topology).isInfrastructure(connectPoint);
+    }
+
+    @Override
+    public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
+        checkNotNull(connectPoint, CONNECTION_POINT_NULL);
+        return defaultTopology(topology).isBroadcastPoint(connectPoint);
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentAccumulator.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentAccumulator.java
new file mode 100644
index 0000000..0328383
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentAccumulator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.Maps;
+import org.onlab.util.AbstractAccumulator;
+import org.onosproject.net.intent.IntentBatchDelegate;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.Key;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+
+/**
+ * An accumulator for building batches of intent operations for virtual network.
+ * Only one batch should be in process per instance at a time.
+ */
+public class VirtualIntentAccumulator extends AbstractAccumulator<IntentData> {
+    private static final int DEFAULT_MAX_EVENTS = 1000;
+    private static final int DEFAULT_MAX_IDLE_MS = 10;
+    private static final int DEFAULT_MAX_BATCH_MS = 50;
+
+    // FIXME: Replace with a system-wide timer instance;
+    // TODO: Convert to use HashedWheelTimer or produce a variant of that; then decide which we want to adopt
+    private static final Timer TIMER = new Timer("virtual-intent-op-batching");
+
+    private final IntentBatchDelegate delegate;
+
+    private volatile boolean ready;
+
+    /**
+     * Creates an intent operation accumulator.
+     *
+     * @param delegate the intent batch delegate
+     */
+    public VirtualIntentAccumulator(IntentBatchDelegate delegate) {
+        super(TIMER, DEFAULT_MAX_EVENTS, DEFAULT_MAX_BATCH_MS, DEFAULT_MAX_IDLE_MS);
+        this.delegate = delegate;
+        // Assume that the delegate is ready for work at the start
+        ready = true; //TODO validate the assumption that delegate is ready
+    }
+
+    @Override
+    public void processItems(List<IntentData> items) {
+        ready = false;
+        delegate.execute(reduce(items));
+    }
+
+    private Collection<IntentData> reduce(List<IntentData> ops) {
+        Map<Key, IntentData> map = Maps.newHashMap();
+        for (IntentData op : ops) {
+            map.put(op.key(), op);
+        }
+        //TODO check the version... or maybe store will handle this.
+        return map.values();
+    }
+
+    @Override
+    public boolean isReady() {
+        return ready;
+    }
+
+    public void ready() {
+        ready = true;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentCompilerRegistry.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentCompilerRegistry.java
new file mode 100644
index 0000000..f922c22
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentCompilerRegistry.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.intent.VirtualIntentCompiler;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentException;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public final class VirtualIntentCompilerRegistry {
+    private final ConcurrentMap<Class<? extends Intent>,
+                VirtualIntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
+
+    // non-instantiable (except for our Singleton)
+    private VirtualIntentCompilerRegistry() {
+
+    }
+
+    public static VirtualIntentCompilerRegistry getInstance() {
+        return SingletonHelper.INSTANCE;
+    }
+
+    /**
+     * Registers the specified compiler for the given intent class.
+     *
+     * @param cls      intent class
+     * @param compiler intent compiler
+     * @param <T>      the type of intent
+     */
+    public <T extends Intent> void registerCompiler(Class<T> cls,
+                                                    VirtualIntentCompiler<T> compiler) {
+        compilers.put(cls, compiler);
+    }
+
+    /**
+     * Unregisters the compiler for the specified intent class.
+     *
+     * @param cls intent class
+     * @param <T> the type of intent
+     */
+    public <T extends Intent> void unregisterCompiler(Class<T> cls) {
+        compilers.remove(cls);
+    }
+
+    /**
+     * Returns immutable set of bindings of currently registered intent compilers.
+     *
+     * @return the set of compiler bindings
+     */
+    public Map<Class<? extends Intent>, VirtualIntentCompiler<? extends Intent>> getCompilers() {
+        return ImmutableMap.copyOf(compilers);
+    }
+
+    /**
+     * Compiles an intent recursively.
+     *
+     * @param networkId network identifier
+     * @param intent intent
+     * @param previousInstallables previous intent installables
+     * @return result of compilation
+     */
+    public List<Intent> compile(NetworkId networkId,
+                         Intent intent, List<Intent> previousInstallables) {
+        if (intent.isInstallable()) {
+            return ImmutableList.of(intent);
+        }
+
+        // FIXME: get previous resources
+        List<Intent> installables = new ArrayList<>();
+        Queue<Intent> compileQueue = new LinkedList<>();
+        compileQueue.add(intent);
+
+        Intent compiling;
+        while ((compiling = compileQueue.poll()) != null) {
+            registerSubclassCompilerIfNeeded(compiling);
+
+            List<Intent> compiled = getCompiler(compiling)
+                    .compile(networkId, compiling, previousInstallables);
+
+            compiled.forEach(i -> {
+                if (i.isInstallable()) {
+                    installables.add(i);
+                } else {
+                    compileQueue.add(i);
+                }
+            });
+        }
+        return installables;
+    }
+
+    /**
+     * Returns the corresponding intent compiler to the specified intent.
+     *
+     * @param intent intent
+     * @param <T>    the type of intent
+     * @return intent compiler corresponding to the specified intent
+     */
+    private <T extends Intent> VirtualIntentCompiler<T> getCompiler(T intent) {
+        @SuppressWarnings("unchecked")
+        VirtualIntentCompiler<T> compiler =
+                (VirtualIntentCompiler<T>) compilers.get(intent.getClass());
+        if (compiler == null) {
+            throw new IntentException("no compiler for class " + intent.getClass());
+        }
+        return compiler;
+    }
+
+    /**
+     * Registers an intent compiler of the specified intent if an intent compiler
+     * for the intent is not registered. This method traverses the class hierarchy of
+     * the intent. Once an intent compiler for a parent type is found, this method
+     * registers the found intent compiler.
+     *
+     * @param intent intent
+     */
+    private void registerSubclassCompilerIfNeeded(Intent intent) {
+        if (!compilers.containsKey(intent.getClass())) {
+            Class<?> cls = intent.getClass();
+            while (cls != Object.class) {
+                // As long as we're within the Intent class descendants
+                if (Intent.class.isAssignableFrom(cls)) {
+                    VirtualIntentCompiler<?> compiler = compilers.get(cls);
+                    if (compiler != null) {
+                        compilers.put(intent.getClass(), compiler);
+                        return;
+                    }
+                }
+                cls = cls.getSuperclass();
+            }
+        }
+    }
+
+    /**
+     * Prevents object instantiation from external.
+     */
+    private static final class SingletonHelper {
+        private static final String ILLEGAL_ACCESS_MSG =
+                "Should not instantiate this class.";
+        private static final VirtualIntentCompilerRegistry INSTANCE =
+                new VirtualIntentCompilerRegistry();
+
+        private SingletonHelper() {
+            throw new IllegalAccessError(ILLEGAL_ACCESS_MSG);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallCoordinator.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallCoordinator.java
new file mode 100644
index 0000000..4a09250
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallCoordinator.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.incubator.net.virtual.impl.VirtualNetworkIntentManager;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentInstallationContext;
+import org.onosproject.net.intent.IntentInstaller;
+import org.onosproject.net.intent.IntentOperationContext;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.onosproject.net.intent.IntentState.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of IntentInstallCoordinator for virtual network.
+ */
+public class VirtualIntentInstallCoordinator {
+    private static final String INSTALLER_NOT_FOUND = "Intent installer not found, Intent: {}";
+    private final Logger log = getLogger(VirtualNetworkIntentManager.class);
+
+    NetworkId networkId;
+    private VirtualIntentInstallerRegistry installerRegistry;
+    private VirtualNetworkIntentStore intentStore;
+
+    /**
+     * Creates an InstallCoordinator.
+     *
+     * @param networkId virtual network identifier
+     * @param installerRegistry the installer registry
+     * @param intentStore the Intent store
+     */
+    public VirtualIntentInstallCoordinator(NetworkId networkId,
+                                           VirtualIntentInstallerRegistry installerRegistry,
+                                           VirtualNetworkIntentStore intentStore) {
+        this.networkId = networkId;
+        this.installerRegistry = installerRegistry;
+        this.intentStore = intentStore;
+    }
+
+    /**
+     * Applies Intent data to be uninstalled and to be installed.
+     *
+     * @param toUninstall Intent data to be uninstalled
+     * @param toInstall Intent data to be installed
+     */
+    public void installIntents(Optional<IntentData> toUninstall,
+                               Optional<IntentData> toInstall) {
+        // If no any Intents to be uninstalled or installed, ignore it.
+        if (!toUninstall.isPresent() && !toInstall.isPresent()) {
+            return;
+        }
+
+        // Classify installable Intents to different installers.
+        ArrayListMultimap<IntentInstaller, Intent> uninstallInstallers;
+        ArrayListMultimap<IntentInstaller, Intent> installInstallers;
+        Set<IntentInstaller> allInstallers = Sets.newHashSet();
+
+        if (toUninstall.isPresent()) {
+            uninstallInstallers = getInstallers(toUninstall.get());
+            allInstallers.addAll(uninstallInstallers.keySet());
+        } else {
+            uninstallInstallers = ArrayListMultimap.create();
+        }
+
+        if (toInstall.isPresent()) {
+            installInstallers = getInstallers(toInstall.get());
+            allInstallers.addAll(installInstallers.keySet());
+        } else {
+            installInstallers = ArrayListMultimap.create();
+        }
+
+        // Generates an installation context for the high level Intent.
+        IntentInstallationContext installationContext =
+                new IntentInstallationContext(toUninstall.orElse(null), toInstall.orElse(null));
+
+        //Generates different operation context for different installable Intents.
+        Map<IntentInstaller, IntentOperationContext> contexts = Maps.newHashMap();
+        allInstallers.forEach(installer -> {
+            List<Intent> intentsToUninstall = uninstallInstallers.get(installer);
+            List<Intent> intentsToInstall = installInstallers.get(installer);
+
+            // Connect context to high level installation context
+            IntentOperationContext context =
+                    new IntentOperationContext(intentsToUninstall, intentsToInstall,
+                                               installationContext);
+            installationContext.addPendingContext(context);
+            contexts.put(installer, context);
+        });
+
+        // Apply contexts to installers
+        contexts.forEach((installer, context) -> {
+            installer.apply(context);
+        });
+    }
+
+    /**
+     * Generates a mapping for installable Intents to installers.
+     *
+     * @param intentData the Intent data which contains installable Intents
+     * @return the mapping for installable Intents to installers
+     */
+    private ArrayListMultimap<IntentInstaller, Intent> getInstallers(IntentData intentData) {
+        ArrayListMultimap<IntentInstaller, Intent> intentInstallers = ArrayListMultimap.create();
+        intentData.installables().forEach(intent -> {
+            IntentInstaller installer = installerRegistry.getInstaller(intent.getClass());
+            if (installer != null) {
+                intentInstallers.put(installer, intent);
+            } else {
+                log.warn(INSTALLER_NOT_FOUND, intent);
+            }
+        });
+        return intentInstallers;
+    }
+
+    /**
+     * Handles success operation context.
+     *
+     * @param context the operation context
+     */
+    public void success(IntentOperationContext context) {
+        IntentInstallationContext intentInstallationContext =
+                context.intentInstallationContext();
+        intentInstallationContext.removePendingContext(context);
+
+        if (intentInstallationContext.isPendingContextsEmpty()) {
+            finish(intentInstallationContext);
+        }
+    }
+
+    /**
+     * Handles failed operation context.
+     *
+     * @param context the operation context
+     */
+    public void failed(IntentOperationContext context) {
+        IntentInstallationContext intentInstallationContext =
+                context.intentInstallationContext();
+        intentInstallationContext.addErrorContext(context);
+        intentInstallationContext.removePendingContext(context);
+
+        if (intentInstallationContext.isPendingContextsEmpty()) {
+            finish(intentInstallationContext);
+        }
+    }
+
+    /**
+     * Completed the installation context and update the Intent store.
+     *
+     * @param intentInstallationContext the installation context
+     */
+    private void finish(IntentInstallationContext intentInstallationContext) {
+        Set<IntentOperationContext> errCtxs = intentInstallationContext.errorContexts();
+        Optional<IntentData> toUninstall = intentInstallationContext.toUninstall();
+        Optional<IntentData> toInstall = intentInstallationContext.toInstall();
+
+        // Intent install success
+        if (errCtxs == null || errCtxs.isEmpty()) {
+            if (toInstall.isPresent()) {
+                IntentData installData = toInstall.get();
+                log.debug("Completed installing: {}", installData.key());
+                installData = new IntentData(installData, installData.installables());
+                installData.setState(INSTALLED);
+                intentStore.write(networkId, installData);
+            } else if (toUninstall.isPresent()) {
+                IntentData uninstallData = toUninstall.get();
+                uninstallData = new IntentData(uninstallData, Collections.emptyList());
+                log.debug("Completed withdrawing: {}", uninstallData.key());
+                switch (uninstallData.request()) {
+                    case INSTALL_REQ:
+                        log.warn("{} was requested to withdraw during installation?",
+                                 uninstallData.intent());
+                        uninstallData.setState(FAILED);
+                        break;
+                    case WITHDRAW_REQ:
+                    default: //TODO "default" case should not happen
+                        uninstallData.setState(WITHDRAWN);
+                        break;
+                }
+                // Intent has been withdrawn; we can clear the installables
+                intentStore.write(networkId, uninstallData);
+            }
+        } else {
+            // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT)
+            if (toInstall.isPresent()) {
+                IntentData installData = toInstall.get();
+                installData.setState(CORRUPT);
+                installData.incrementErrorCount();
+                intentStore.write(networkId, installData);
+            }
+            // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
+            if (toUninstall.isPresent()) {
+                IntentData uninstallData = toUninstall.get();
+                uninstallData.setState(CORRUPT);
+                uninstallData.incrementErrorCount();
+                intentStore.write(networkId, uninstallData);
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallerRegistry.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallerRegistry.java
new file mode 100644
index 0000000..a0e6448
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentInstallerRegistry.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentInstaller;
+
+import java.util.Map;
+
+/**
+ * The local registry for Intent installer for virtual networks.
+ */
+public final class VirtualIntentInstallerRegistry {
+    private final Map<Class<? extends Intent>,
+                IntentInstaller<? extends Intent>> installers;
+
+    // non-instantiable (except for our Singleton)
+    private VirtualIntentInstallerRegistry() {
+        installers = Maps.newConcurrentMap();
+    }
+
+    public static VirtualIntentInstallerRegistry getInstance() {
+        return SingletonHelper.INSTANCE;
+    }
+
+    /**
+     * Registers the specific installer for the given intent class.
+     *
+     * @param cls intent class
+     * @param installer intent installer
+     * @param <T> the type of intent
+     */
+    public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+        installers.put(cls, installer);
+    }
+
+    /**
+     * Unregisters the installer for the specific intent class.
+     *
+     * @param cls intent class
+     * @param <T> the type of intent
+     */
+    public <T extends Intent> void unregisterInstaller(Class<T> cls) {
+        installers.remove(cls);
+    }
+
+    /**
+     * Returns immutable set of binding of currently registered intent installers.
+     *
+     * @return the set of installer bindings
+     */
+    public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
+        return ImmutableMap.copyOf(installers);
+    }
+
+    /**
+     * Get an Intent installer by given Intent type.
+     *
+     * @param cls the Intent type
+     * @param <T> the Intent type
+     * @return the Intent installer of the Intent type if exists; null otherwise
+     */
+    public <T extends Intent> IntentInstaller<T> getInstaller(Class<T> cls) {
+        return (IntentInstaller<T>) installers.get(cls);
+    }
+
+    /**
+     * Prevents object instantiation from external.
+     */
+    private static final class SingletonHelper {
+        private static final String ILLEGAL_ACCESS_MSG =
+                "Should not instantiate this class.";
+        private static final VirtualIntentInstallerRegistry INSTANCE =
+                new VirtualIntentInstallerRegistry();
+
+        private SingletonHelper() {
+            throw new IllegalAccessError(ILLEGAL_ACCESS_MSG);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentProcessor.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentProcessor.java
new file mode 100644
index 0000000..a7719f9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentProcessor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A collection of methods to process an intent for virtual networks.
+ *
+ * This interface is public, but intended to be used only by IntentManager and
+ * IntentProcessPhase subclasses stored under phase package.
+ */
+public interface VirtualIntentProcessor {
+    /**
+     * Compiles an intent recursively.
+     *
+     * @param networkId virtual network identifier
+     * @param intent intent
+     * @param previousInstallables previous intent installables
+     * @return result of compilation
+     */
+    List<Intent> compile(NetworkId networkId, Intent intent, List<Intent> previousInstallables);
+
+    /**
+     * Applies intents.
+     *
+     * @param networkId virtual network identifier
+     * @param toUninstall Intent data describing flows to uninstall.
+     * @param toInstall Intent data describing flows to install.
+     */
+    void apply(NetworkId networkId, Optional<IntentData> toUninstall,
+               Optional<IntentData> toInstall);
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentSkipped.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentSkipped.java
new file mode 100644
index 0000000..df7f451
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/VirtualIntentSkipped.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent;
+
+import org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualFinalIntentProcessPhase;
+import org.onosproject.net.intent.IntentData;
+
+/**
+ * Represents a phase where an intent is not compiled for a virtual network.
+ * This should be used if a new version of the intent will immediately override
+ * this one.
+ */
+public final class VirtualIntentSkipped extends VirtualFinalIntentProcessPhase {
+
+    private static final VirtualIntentSkipped SINGLETON = new VirtualIntentSkipped();
+
+    /**
+     * Returns a shared skipped phase.
+     *
+     * @return skipped phase
+     */
+    public static VirtualIntentSkipped getPhase() {
+        return SINGLETON;
+    }
+
+    // Prevent object construction; use getPhase()
+    private VirtualIntentSkipped() {
+    }
+
+    @Override
+    public IntentData data() {
+        return null;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/package-info.java
new file mode 100644
index 0000000..c6758ae
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Core subsystem for tracking high-level intents for treatment of selected
+ * network traffic for virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.impl.intent;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualFinalIntentProcessPhase.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualFinalIntentProcessPhase.java
new file mode 100644
index 0000000..978c95c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualFinalIntentProcessPhase.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+/**
+ * Represents a final phase of processing an intent for virtual networks.
+ */
+public abstract class VirtualFinalIntentProcessPhase
+        implements VirtualIntentProcessPhase {
+
+    @Override
+    public final Optional<VirtualIntentProcessPhase> execute() {
+        preExecute();
+        return Optional.empty();
+    }
+
+    /**
+     * Executes operations that must take place before the phase starts.
+     */
+    protected void preExecute() {}
+
+    /**
+     * Returns the IntentData object being acted on by this phase.
+     *
+     * @return intent data object for the phase
+     */
+    public abstract IntentData data();
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentCompiling.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentCompiling.java
new file mode 100644
index 0000000..639c04c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentCompiling.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a phase where an intent is being compiled or recompiled
+ * for virtual networks.
+ */
+public class VirtualIntentCompiling implements VirtualIntentProcessPhase {
+    private static final Logger log = LoggerFactory.getLogger(VirtualIntentCompiling.class);
+
+    private final NetworkId networkId;
+    private final VirtualIntentProcessor processor;
+    private final IntentData data;
+    private final Optional<IntentData> stored;
+
+    /**
+     * Creates a intent recompiling phase.
+     *
+     * @param networkId virtual network identifier
+     * @param processor intent processor that does work for recompiling
+     * @param data      intent data containing an intent to be recompiled
+     * @param stored    intent data stored in the store
+     */
+    VirtualIntentCompiling(NetworkId networkId, VirtualIntentProcessor processor,
+                           IntentData data, Optional<IntentData> stored) {
+        this.networkId = checkNotNull(networkId);
+        this.processor = checkNotNull(processor);
+        this.data = checkNotNull(data);
+        this.stored = checkNotNull(stored);
+    }
+
+    @Override
+    public Optional<VirtualIntentProcessPhase> execute() {
+        try {
+            List<Intent> compiled = processor
+                    .compile(networkId, data.intent(),
+                             //TODO consider passing an optional here in the future
+                             stored.map(IntentData::installables).orElse(null));
+            return Optional.of(new VirtualIntentInstalling(networkId, processor,
+                                                           IntentData.compiled(data, compiled), stored));
+        } catch (IntentException e) {
+            log.warn("Unable to compile intent {} due to:", data.intent(), e);
+            if (stored.filter(x -> !x.installables().isEmpty()).isPresent()) {
+                // removing orphaned flows and deallocating resources
+                return Optional.of(
+                        new VirtualIntentWithdrawing(networkId, processor,
+                                                     new IntentData(data, stored.get().installables())));
+            } else {
+                return Optional.of(new VirtualIntentFailed(data));
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentFailed.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentFailed.java
new file mode 100644
index 0000000..dff1d13
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentFailed.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.FAILED;
+
+/**
+ * Represents a phase where the compile has failed.
+ */
+public class VirtualIntentFailed extends VirtualFinalIntentProcessPhase {
+
+    private final IntentData data;
+
+    /**
+     * Create an instance with the specified data.
+     *
+     * @param data intentData
+     */
+    VirtualIntentFailed(IntentData data) {
+        this.data = IntentData.nextState(checkNotNull(data), FAILED);
+    }
+
+    @Override
+    public IntentData data() {
+        return data;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstallRequest.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstallRequest.java
new file mode 100644
index 0000000..08bfec7
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstallRequest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase.transferErrorCount;
+
+/**
+ * Represents a phase where intent installation has been requested
+ * for a virtual network.
+ */
+final class VirtualIntentInstallRequest implements VirtualIntentProcessPhase {
+
+    private final NetworkId networkId;
+    private final VirtualIntentProcessor processor;
+    private final IntentData data;
+    private final Optional<IntentData> stored;
+
+    /**
+     * Creates an install request phase.
+     *
+     * @param networkId virtual network identifier
+     * @param processor  intent processor to be passed to intent process phases
+     *                   generated after this phase
+     * @param intentData intent data to be processed
+     * @param stored     intent data stored in the store
+     */
+    VirtualIntentInstallRequest(NetworkId networkId, VirtualIntentProcessor processor,
+                                IntentData intentData, Optional<IntentData> stored) {
+        this.networkId = checkNotNull(networkId);
+        this.processor = checkNotNull(processor);
+        this.data = checkNotNull(intentData);
+        this.stored = checkNotNull(stored);
+    }
+
+    @Override
+    public Optional<VirtualIntentProcessPhase> execute() {
+        transferErrorCount(data, stored);
+
+        return Optional.of(new VirtualIntentCompiling(networkId, processor, data, stored));
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstalling.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstalling.java
new file mode 100644
index 0000000..cd8a185
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentInstalling.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.INSTALLING;
+
+/**
+ * Represents a phase where an intent is being installed for a virtual network.
+ */
+//FIXME: better way to implement intent phase and processing for virtual networks?
+public class VirtualIntentInstalling extends VirtualFinalIntentProcessPhase {
+
+    private final NetworkId networkId;
+    private final VirtualIntentProcessor processor;
+    private final IntentData data;
+    private final Optional<IntentData> stored;
+
+    /**
+     * Create an installing phase.
+     *
+     * @param networkId virtual network identifier
+     * @param processor intent processor that does work for installing
+     * @param data      intent data containing an intent to be installed
+     * @param stored    intent data already stored
+     */
+    VirtualIntentInstalling(NetworkId networkId, VirtualIntentProcessor processor,
+                            IntentData data,
+                            Optional<IntentData> stored) {
+        this.networkId = checkNotNull(networkId);
+        this.processor = checkNotNull(processor);
+        this.data = checkNotNull(data);
+        this.stored = checkNotNull(stored);
+        this.data.setState(INSTALLING);
+    }
+
+    @Override
+    public void preExecute() {
+        processor.apply(networkId, stored, Optional.of(data));
+    }
+
+    @Override
+    public IntentData data() {
+        return data;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentProcessPhase.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentProcessPhase.java
new file mode 100644
index 0000000..99fab54
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentProcessPhase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Represents a phase of processing an intent.
+ */
+public interface VirtualIntentProcessPhase {
+    /**
+     * Execute the procedure represented by the instance
+     * and generates the next update instance.
+     *
+     * @return next update
+     */
+    Optional<VirtualIntentProcessPhase> execute();
+
+    /**
+     * Create a starting intent process phase according to intent data this class holds.
+     *
+     * @param networkId virtual network identifier
+     * @param processor intent processor to be passed to intent process phases
+     *                  generated while this instance is working
+     * @param data intent data to be processed
+     * @param current intent date that is stored in the store
+     * @return starting intent process phase
+     */
+    static VirtualIntentProcessPhase newInitialPhase(NetworkId networkId,
+                                                     VirtualIntentProcessor processor,
+                                                     IntentData data, IntentData current) {
+        switch (data.request()) {
+            case INSTALL_REQ:
+                return new VirtualIntentInstallRequest(networkId, processor, data,
+                                                       Optional.ofNullable(current));
+            case WITHDRAW_REQ:
+                return new VirtualIntentWithdrawRequest(networkId, processor, data,
+                                                        Optional.ofNullable(current));
+            case PURGE_REQ:
+                return new VirtualIntentPurgeRequest(data, Optional.ofNullable(current));
+            default:
+                // illegal state
+                return new VirtualIntentFailed(data);
+        }
+    }
+
+    static VirtualFinalIntentProcessPhase process(VirtualIntentProcessPhase initial) {
+        Optional<VirtualIntentProcessPhase> currentPhase = Optional.of(initial);
+        VirtualIntentProcessPhase previousPhase = initial;
+
+        while (currentPhase.isPresent()) {
+            previousPhase = currentPhase.get();
+            currentPhase = previousPhase.execute();
+        }
+        return (VirtualFinalIntentProcessPhase) previousPhase;
+    }
+
+    static void transferErrorCount(IntentData data, Optional<IntentData> stored) {
+        stored.ifPresent(storedData -> {
+            if (Objects.equals(data.intent(), storedData.intent()) &&
+                    Objects.equals(data.request(), storedData.request())) {
+                data.setErrorCount(storedData.errorCount());
+            } else {
+                data.setErrorCount(0);
+            }
+        });
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentPurgeRequest.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentPurgeRequest.java
new file mode 100644
index 0000000..a6f29ea
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentPurgeRequest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentState;
+import org.slf4j.Logger;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Represents a phase of requesting a purge of an intent for a virtual network.
+ * Note: The purge will only succeed if the intent is FAILED or WITHDRAWN.
+ */
+final class VirtualIntentPurgeRequest extends VirtualFinalIntentProcessPhase {
+    private static final Logger log = getLogger(VirtualIntentPurgeRequest.class);
+
+    private final IntentData data;
+    protected final Optional<IntentData> stored;
+
+    VirtualIntentPurgeRequest(IntentData intentData, Optional<IntentData> stored) {
+        this.data = checkNotNull(intentData);
+        this.stored = checkNotNull(stored);
+    }
+
+    private boolean shouldAcceptPurge() {
+        if (!stored.isPresent()) {
+            log.info("Purge for intent {}, but intent is not present",
+                     data.key());
+            return true;
+        }
+
+        IntentData storedData = stored.get();
+        if (storedData.state() == IntentState.WITHDRAWN
+                || storedData.state() == IntentState.FAILED) {
+            return true;
+        }
+        log.info("Purge for intent {} is rejected because intent state is {}",
+                 data.key(), storedData.state());
+        return false;
+    }
+
+    @Override
+    public IntentData data() {
+        if (shouldAcceptPurge()) {
+            return data;
+        } else {
+            return stored.get();
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawRequest.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawRequest.java
new file mode 100644
index 0000000..ce8dfc9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.virtual.impl.intent.phase.VirtualIntentProcessPhase.transferErrorCount;
+
+/**
+ * Represents a phase of requesting a withdraw of an intent for a virtual network.
+ */
+final class VirtualIntentWithdrawRequest implements VirtualIntentProcessPhase {
+    private static final Logger log = LoggerFactory.getLogger(VirtualIntentWithdrawRequest.class);
+
+    private final NetworkId networkId;
+    private final VirtualIntentProcessor processor;
+    private final IntentData data;
+    private final Optional<IntentData> stored;
+
+    /**
+     * Creates a withdraw request phase.
+     *
+     * @param networkId virtual network identifier
+     * @param processor  intent processor to be passed to intent process phases
+     *                   generated after this phase
+     * @param intentData intent data to be processed
+     * @param stored     intent data stored in the store
+     */
+    VirtualIntentWithdrawRequest(NetworkId networkId, VirtualIntentProcessor processor,
+                                 IntentData intentData, Optional<IntentData> stored) {
+        this.networkId = checkNotNull(networkId);
+        this.processor = checkNotNull(processor);
+        this.data = checkNotNull(intentData);
+        this.stored = checkNotNull(stored);
+    }
+
+    @Override
+    public Optional<VirtualIntentProcessPhase> execute() {
+        //TODO perhaps we want to validate that the pending and current are the
+        // same version i.e. they are the same
+        // Note: this call is not just the symmetric version of submit
+
+        transferErrorCount(data, stored);
+
+        if (!stored.isPresent() || stored.get().installables().isEmpty()) {
+            switch (data.request()) {
+                case INSTALL_REQ:
+                    // illegal state?
+                    log.warn("{} was requested to withdraw during installation?", data.intent());
+                    return Optional.of(new VirtualIntentFailed(data));
+                case WITHDRAW_REQ:
+                default: //TODO "default" case should not happen
+                    return Optional.of(new VirtualIntentWithdrawn(data));
+            }
+        }
+
+        return Optional.of(new VirtualIntentWithdrawing(networkId, processor,
+                                                        new IntentData(data, stored.get().installables())));
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawing.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawing.java
new file mode 100644
index 0000000..f7a2867
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawing.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.impl.intent.VirtualIntentProcessor;
+import org.onosproject.net.intent.IntentData;
+
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.WITHDRAWING;
+
+/**
+ * Represents a phase where an intent is withdrawing.
+ */
+final class VirtualIntentWithdrawing extends VirtualFinalIntentProcessPhase {
+
+    private final NetworkId networkId;
+    private final VirtualIntentProcessor processor;
+    private final IntentData data;
+
+    /**
+     * Creates a withdrawing phase.
+     *
+     * @param networkId virtual network identifier
+     * @param processor intent processor that does work for withdrawing
+     * @param data      intent data containing an intent to be withdrawn
+     */
+    VirtualIntentWithdrawing(NetworkId networkId, VirtualIntentProcessor processor,
+                             IntentData data) {
+        this.networkId = checkNotNull(networkId);
+        this.processor = checkNotNull(processor);
+        this.data = checkNotNull(data);
+        this.data.setState(WITHDRAWING);
+    }
+
+    @Override
+    protected void preExecute() {
+        processor.apply(networkId, Optional.of(data), Optional.empty());
+    }
+
+    @Override
+    public IntentData data() {
+        return data;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawn.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawn.java
new file mode 100644
index 0000000..7e592c2
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/VirtualIntentWithdrawn.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
+
+import org.onosproject.net.intent.IntentData;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.WITHDRAWN;
+
+/**
+ * Represents a phase where an intent has been withdrawn for a virtual network.
+ */
+final class VirtualIntentWithdrawn extends VirtualFinalIntentProcessPhase {
+
+    private final IntentData data;
+
+    /**
+     * Create a withdrawn phase.
+     *
+     * @param data intent data containing an intent to be withdrawn
+     */
+    VirtualIntentWithdrawn(IntentData data) {
+        this.data = IntentData.nextState(checkNotNull(data), WITHDRAWN);
+    }
+
+    @Override
+    public IntentData data() {
+        return data;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/package-info.java
new file mode 100644
index 0000000..5fac168
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/intent/phase/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implementations of various intent processing phases for virtual networks.
+ */
+package org.onosproject.incubator.net.virtual.impl.intent.phase;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/package-info.java
new file mode 100644
index 0000000..b0750eb
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of the virtual network subsystem.
+ */
+package org.onosproject.incubator.net.virtual.impl;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
new file mode 100644
index 0000000..436e1d4
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProvider.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.InternalRoutingAlgorithm;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.BatchOperationEntry;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.TopologyService;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.copyOf;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider that translate virtual flow rules into physical rules.
+ * Current implementation is based on FlowRules.
+ * This virtualize and de-virtualize virtual flow rules into physical flow rules.
+ * {@link org.onosproject.net.flow.FlowRule}
+ */
+@Component(service = VirtualFlowRuleProvider.class)
+public class DefaultVirtualFlowRuleProvider extends AbstractVirtualProvider
+        implements VirtualFlowRuleProvider {
+
+    private static final String APP_ID_STR = "org.onosproject.virtual.vnet-flow_";
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualNetworkService vnService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualProviderRegistryService providerRegistryService;
+
+    private InternalRoutingAlgorithm internalRoutingAlgorithm;
+    private InternalVirtualFlowRuleManager frm;
+    private ApplicationId appId;
+    private FlowRuleListener flowRuleListener;
+
+    /**
+     * Creates a provider with the identifier.
+     */
+    public DefaultVirtualFlowRuleProvider() {
+        super(new ProviderId("vnet-flow", "org.onosproject.virtual.vnet-flow"));
+    }
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication(APP_ID_STR);
+
+        providerRegistryService.registerProvider(this);
+
+        flowRuleListener = new InternalFlowRuleListener();
+        flowRuleService.addListener(flowRuleListener);
+
+        internalRoutingAlgorithm = new DefaultInternalRoutingAlgorithm();
+        frm = new InternalVirtualFlowRuleManager();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        flowRuleService.removeListener(flowRuleListener);
+        flowRuleService.removeFlowRulesById(appId);
+        providerRegistryService.unregisterProvider(this);
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+    }
+
+    @Override
+    public void applyFlowRule(NetworkId networkId, FlowRule... flowRules) {
+        for (FlowRule flowRule : flowRules) {
+            devirtualize(networkId, flowRule).forEach(
+                    r -> flowRuleService.applyFlowRules(r));
+        }
+    }
+
+    @Override
+    public void removeFlowRule(NetworkId networkId, FlowRule... flowRules) {
+        for (FlowRule flowRule : flowRules) {
+            devirtualize(networkId, flowRule).forEach(
+                    r -> flowRuleService.removeFlowRules(r));
+        }
+    }
+
+    @Override
+    public void executeBatch(NetworkId networkId, FlowRuleBatchOperation batch) {
+        checkNotNull(batch);
+
+        for (FlowRuleBatchEntry fop : batch.getOperations()) {
+            FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
+
+            switch (fop.operator()) {
+                case ADD:
+                    devirtualize(networkId, fop.target()).forEach(builder::add);
+                    break;
+                case REMOVE:
+                    devirtualize(networkId, fop.target()).forEach(builder::remove);
+                    break;
+                case MODIFY:
+                    devirtualize(networkId, fop.target()).forEach(builder::modify);
+                    break;
+                default:
+                    break;
+            }
+
+            flowRuleService.apply(builder.build(new FlowRuleOperationsContext() {
+                @Override
+                public void onSuccess(FlowRuleOperations ops) {
+                    CompletedBatchOperation status =
+                            new CompletedBatchOperation(true,
+                                                        Sets.newConcurrentHashSet(),
+                                                        batch.deviceId());
+
+                    VirtualFlowRuleProviderService providerService =
+                            (VirtualFlowRuleProviderService) providerRegistryService
+                                    .getProviderService(networkId,
+                                                        VirtualFlowRuleProvider.class);
+                    providerService.batchOperationCompleted(batch.id(), status);
+                }
+
+                @Override
+                public void onError(FlowRuleOperations ops) {
+                    Set<FlowRule> failures = ImmutableSet.copyOf(
+                            Lists.transform(batch.getOperations(),
+                                            BatchOperationEntry::target));
+
+                    CompletedBatchOperation status =
+                            new CompletedBatchOperation(false,
+                                                        failures,
+                                                        batch.deviceId());
+
+                    VirtualFlowRuleProviderService providerService =
+                            (VirtualFlowRuleProviderService) providerRegistryService
+                                    .getProviderService(networkId,
+                                                        VirtualFlowRuleProvider.class);
+                    providerService.batchOperationCompleted(batch.id(), status);
+                }
+            }));
+        }
+    }
+
+    public void setEmbeddingAlgorithm(InternalRoutingAlgorithm
+                                              internalRoutingAlgorithm) {
+        this.internalRoutingAlgorithm = internalRoutingAlgorithm;
+    }
+
+    /**
+     * Translate the requested physical flow rules into virtual flow rules.
+     *
+     * @param flowRule A virtual flow rule to be translated
+     * @return A flow rule for a specific virtual network
+     */
+    private FlowRule virtualizeFlowRule(FlowRule flowRule) {
+
+        FlowRule storedrule = frm.getVirtualRule(flowRule);
+
+        if (flowRule.reason() == FlowRule.FlowRemoveReason.NO_REASON) {
+            return storedrule;
+        } else {
+            return DefaultFlowRule.builder()
+                    .withReason(flowRule.reason())
+                    .withPriority(storedrule.priority())
+                    .forDevice(storedrule.deviceId())
+                    .forTable(storedrule.tableId())
+                    .fromApp(new DefaultApplicationId(storedrule.appId(), null))
+                    .withIdleTimeout(storedrule.timeout())
+                    .withHardTimeout(storedrule.hardTimeout())
+                    .withSelector(storedrule.selector())
+                    .withTreatment(storedrule.treatment())
+                    .build();
+        }
+    }
+
+    private FlowEntry virtualize(FlowEntry flowEntry) {
+        FlowRule vRule = virtualizeFlowRule(flowEntry);
+
+        if (vRule == null) {
+            return null;
+        }
+
+        FlowEntry vEntry = new DefaultFlowEntry(vRule, flowEntry.state(),
+                                                flowEntry.life(),
+                                                flowEntry.packets(),
+                                                flowEntry.bytes());
+        return vEntry;
+    }
+
+    /**
+     * Translate the requested virtual flow rules into physical flow rules.
+     * The translation could be one to many.
+     *
+     * @param flowRule A flow rule from underlying data plane to be translated
+     * @return A set of flow rules for physical network
+     */
+    private Set<FlowRule> devirtualize(NetworkId networkId, FlowRule flowRule) {
+
+        Set<FlowRule> outRules = new HashSet<>();
+
+        Set<ConnectPoint> ingressPoints = extractIngressPoints(networkId,
+                                                               flowRule.deviceId(),
+                                                               flowRule.selector());
+
+        ConnectPoint egressPoint = extractEgressPoints(networkId,
+                                                         flowRule.deviceId(),
+                                                         flowRule.treatment());
+
+        if (egressPoint == null) {
+            return outRules;
+        }
+
+        TrafficSelector.Builder commonSelectorBuilder
+                = DefaultTrafficSelector.builder();
+        flowRule.selector().criteria().stream()
+                .filter(c -> c.type() != Criterion.Type.IN_PORT)
+                .forEach(c -> commonSelectorBuilder.add(c));
+        TrafficSelector commonSelector = commonSelectorBuilder.build();
+
+        TrafficTreatment.Builder commonTreatmentBuilder
+                = DefaultTrafficTreatment.builder();
+        flowRule.treatment().allInstructions().stream()
+                .filter(i -> i.type() != Instruction.Type.OUTPUT)
+                .forEach(i -> commonTreatmentBuilder.add(i));
+        TrafficTreatment commonTreatment = commonTreatmentBuilder.build();
+
+        for (ConnectPoint ingressPoint : ingressPoints) {
+            if (egressPoint.port() == PortNumber.FLOOD) {
+                Set<ConnectPoint> outPoints = vnService
+                        .getVirtualPorts(networkId, flowRule.deviceId())
+                        .stream()
+                        .map(VirtualPort::realizedBy)
+                        .filter(p -> !p.equals(ingressPoint))
+                        .collect(Collectors.toSet());
+
+                for (ConnectPoint outPoint : outPoints) {
+                    outRules.addAll(generateRules(networkId, ingressPoint, outPoint,
+                                                  commonSelector, commonTreatment, flowRule));
+                }
+            } else {
+                outRules.addAll(generateRules(networkId, ingressPoint, egressPoint,
+                                              commonSelector, commonTreatment, flowRule));
+            }
+        }
+
+        return outRules;
+    }
+
+    /**
+     * Extract ingress connect points of the physical network
+     * from the requested traffic selector.
+     *
+     * @param networkId the virtual network identifier
+     * @param deviceId the virtual device identifier
+     * @param selector the traffic selector to extract ingress point
+     * @return the set of ingress connect points of the physical network
+     */
+    private Set<ConnectPoint> extractIngressPoints(NetworkId networkId,
+                                                   DeviceId deviceId,
+                                                   TrafficSelector selector) {
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+
+        Set<VirtualPort> vPorts = vnService
+                .getVirtualPorts(networkId, deviceId);
+
+        PortCriterion portCriterion = ((PortCriterion) selector
+                .getCriterion(Criterion.Type.IN_PORT));
+
+        if (portCriterion != null) {
+            PortNumber vInPortNum = portCriterion.port();
+
+            Optional<ConnectPoint> optionalCp =  vPorts.stream()
+                    .filter(v -> v.number().equals(vInPortNum))
+                    .map(VirtualPort::realizedBy).findFirst();
+            if (!optionalCp.isPresent()) {
+                log.warn("Port {} is not realized yet, in Network {}, Device {}",
+                         vInPortNum, networkId, deviceId);
+                return ingressPoints;
+            }
+
+            ingressPoints.add(optionalCp.get());
+        } else {
+            for (VirtualPort vPort : vPorts) {
+                if (vPort.realizedBy() != null) {
+                    ingressPoints.add(vPort.realizedBy());
+                } else {
+                    log.warn("Port {} is not realized yet, in Network {}, " +
+                                     "Device {}",
+                             vPort, networkId, deviceId);
+                }
+            }
+        }
+
+        return ingressPoints;
+    }
+
+    /**
+     * Extract egress connect point of the physical network
+     * from the requested traffic treatment.
+     *
+     * @param networkId the virtual network identifier
+     * @param deviceId the virtual device identifier
+     * @param treatment the traffic treatment to extract ingress point
+     * @return the egress connect point of the physical network
+     */
+    private ConnectPoint extractEgressPoints(NetworkId networkId,
+                                                  DeviceId deviceId,
+                                                  TrafficTreatment treatment) {
+
+        Set<VirtualPort> vPorts = vnService
+                .getVirtualPorts(networkId, deviceId);
+
+        PortNumber vOutPortNum = treatment.allInstructions().stream()
+                .filter(i -> i.type() == Instruction.Type.OUTPUT)
+                .map(i -> ((Instructions.OutputInstruction) i).port())
+                .findFirst().get();
+
+        Optional<ConnectPoint> optionalCpOut = vPorts.stream()
+                .filter(v -> v.number().equals(vOutPortNum))
+                .map(VirtualPort::realizedBy)
+                .findFirst();
+
+        if (!optionalCpOut.isPresent()) {
+            if (vOutPortNum.isLogical()) {
+                return new ConnectPoint(DeviceId.deviceId("vNet"), vOutPortNum);
+            }
+
+            log.warn("Port {} is not realized yet, in Network {}, Device {}",
+                     vOutPortNum, networkId, deviceId);
+            return null;
+        }
+
+        return optionalCpOut.get();
+    }
+
+
+    /**
+     * Generates the corresponding flow rules for the physical network.
+     *
+     * @param networkId The virtual network identifier
+     * @param ingressPoint The ingress point of the physical network
+     * @param egressPoint The egress point of the physical network
+     * @param commonSelector A common traffic selector between the virtual
+     *                       and physical flow rules
+     * @param commonTreatment A common traffic treatment between the virtual
+     *                        and physical flow rules
+     * @param flowRule The virtual flow rule to be translated
+     * @return A set of flow rules for the physical network
+     */
+    private Set<FlowRule> generateRules(NetworkId networkId,
+                                        ConnectPoint ingressPoint,
+                                        ConnectPoint egressPoint,
+                                        TrafficSelector commonSelector,
+                                        TrafficTreatment commonTreatment,
+                                        FlowRule flowRule) {
+
+        if (ingressPoint.deviceId().equals(egressPoint.deviceId()) ||
+                egressPoint.port().isLogical()) {
+            return generateRuleForSingle(networkId, ingressPoint, egressPoint,
+                                         commonSelector, commonTreatment, flowRule);
+        } else {
+            return generateRuleForMulti(networkId, ingressPoint, egressPoint,
+                                         commonSelector, commonTreatment, flowRule);
+        }
+    }
+
+    /**
+     * Generate physical rules when a virtual flow rule can be handled inside
+     * a single physical switch.
+     *
+     * @param networkId The virtual network identifier
+     * @param ingressPoint The ingress point of the physical network
+     * @param egressPoint The egress point of the physical network
+     * @param commonSelector A common traffic selector between the virtual
+     *                       and physical flow rules
+     * @param commonTreatment A common traffic treatment between the virtual
+     *                        and physical flow rules
+     * @param flowRule The virtual flow rule to be translated
+     * @return A set of flow rules for the physical network
+     */
+    private Set<FlowRule> generateRuleForSingle(NetworkId networkId,
+            ConnectPoint ingressPoint,
+            ConnectPoint egressPoint,
+            TrafficSelector commonSelector,
+            TrafficTreatment commonTreatment,
+            FlowRule flowRule) {
+
+        Set<FlowRule> outRules = new HashSet<>();
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector
+                .builder(commonSelector)
+                .matchInPort(ingressPoint.port());
+
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment
+                .builder(commonTreatment)
+                .setOutput(egressPoint.port());
+
+        FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+                .forDevice(ingressPoint.deviceId())
+                .withSelector(selectorBuilder.build())
+                .withTreatment(treatmentBuilder.build())
+                .withIdleTimeout(flowRule.timeout())
+                .withPriority(flowRule.priority());
+
+        FlowRule rule = ruleBuilder.build();
+        frm.addIngressRule(flowRule, rule, networkId);
+        outRules.add(rule);
+
+        return outRules;
+    }
+
+    /**
+     * Generate physical rules when a virtual flow rule can be handled with
+     * multiple physical switches.
+     *
+     * @param networkId The virtual network identifier
+     * @param ingressPoint The ingress point of the physical network
+     * @param egressPoint The egress point of the physical network
+     * @param commonSelector A common traffic selector between the virtual
+     *                       and physical flow rules
+     * @param commonTreatment A common traffic treatment between the virtual
+     *                        and physical flow rules
+     * @param flowRule The virtual flow rule to be translated
+     * @return A set of flow rules for the physical network
+     */
+    private Set<FlowRule> generateRuleForMulti(NetworkId networkId,
+                                                ConnectPoint ingressPoint,
+                                                ConnectPoint egressPoint,
+                                                TrafficSelector commonSelector,
+                                                TrafficTreatment commonTreatment,
+                                                FlowRule flowRule) {
+        Set<FlowRule> outRules = new HashSet<>();
+
+        Path internalPath = internalRoutingAlgorithm
+                .findPath(ingressPoint, egressPoint);
+        checkNotNull(internalPath, "No path between " +
+                ingressPoint.toString() + " " + egressPoint.toString());
+
+        ConnectPoint outCp = internalPath.links().get(0).src();
+
+        //ingress point of tunnel
+        TrafficSelector.Builder selectorBuilder =
+                DefaultTrafficSelector.builder(commonSelector);
+        selectorBuilder.matchInPort(ingressPoint.port());
+
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder(commonTreatment);
+        //TODO: add the logic to check host location
+        treatmentBuilder.pushVlan()
+                .setVlanId(VlanId.vlanId(networkId.id().shortValue()));
+        treatmentBuilder.setOutput(outCp.port());
+
+        FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+                .forDevice(ingressPoint.deviceId())
+                .withSelector(selectorBuilder.build())
+                .withIdleTimeout(flowRule.timeout())
+                .withTreatment(treatmentBuilder.build())
+                .withPriority(flowRule.priority());
+
+        FlowRule rule = ruleBuilder.build();
+        frm.addIngressRule(flowRule, rule, networkId);
+        outRules.add(rule);
+
+        //routing inside tunnel
+        ConnectPoint inCp = internalPath.links().get(0).dst();
+
+        if (internalPath.links().size() > 1) {
+            for (Link l : internalPath.links()
+                    .subList(1, internalPath.links().size())) {
+
+                outCp = l.src();
+
+                selectorBuilder = DefaultTrafficSelector
+                        .builder(commonSelector)
+                        .matchVlanId(VlanId.vlanId(networkId.id().shortValue()))
+                        .matchInPort(inCp.port());
+
+                treatmentBuilder = DefaultTrafficTreatment
+                        .builder(commonTreatment)
+                        .setOutput(outCp.port());
+
+                ruleBuilder = DefaultFlowRule.builder()
+                        .fromApp(vnService.getVirtualNetworkApplicationId(networkId))
+                        .forDevice(inCp.deviceId())
+                        .withSelector(selectorBuilder.build())
+                        .withTreatment(treatmentBuilder.build())
+                        .withIdleTimeout(flowRule.timeout())
+                        .withPriority(flowRule.priority());
+
+                outRules.add(ruleBuilder.build());
+                inCp = l.dst();
+            }
+        }
+
+        //egress point of tunnel
+        selectorBuilder = DefaultTrafficSelector.builder(commonSelector)
+                .matchVlanId(VlanId.vlanId(networkId.id().shortValue()))
+                .matchInPort(inCp.port());
+
+        treatmentBuilder = DefaultTrafficTreatment.builder(commonTreatment)
+                .popVlan()
+                .setOutput(egressPoint.port());
+
+        ruleBuilder = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .forDevice(egressPoint.deviceId())
+                .withSelector(selectorBuilder.build())
+                .withTreatment(treatmentBuilder.build())
+                .withIdleTimeout(flowRule.timeout())
+                .withPriority(flowRule.priority());
+
+        outRules.add(ruleBuilder.build());
+
+        return outRules;
+    }
+
+    private class InternalFlowRuleListener implements FlowRuleListener {
+        @Override
+        public void event(FlowRuleEvent event) {
+            if ((event.type() == FlowRuleEvent.Type.RULE_ADDED) ||
+                    (event.type() == FlowRuleEvent.Type.RULE_UPDATED)) {
+                if (frm.isVirtualIngressRule(event.subject())) {
+                    NetworkId networkId = frm.getVirtualNetworkId(event.subject());
+                    FlowEntry vEntry = getVirtualFlowEntry(event.subject());
+
+                    if (vEntry == null) {
+                        return;
+                    }
+
+                    frm.addOrUpdateFlowEntry(networkId, vEntry.deviceId(), vEntry);
+
+                    VirtualFlowRuleProviderService providerService =
+                            (VirtualFlowRuleProviderService) providerRegistryService
+                                    .getProviderService(networkId,
+                                                        VirtualFlowRuleProvider.class);
+
+                    ImmutableList.Builder<FlowEntry> builder = ImmutableList.builder();
+                    builder.addAll(frm.getFlowEntries(networkId, vEntry.deviceId()));
+
+                    providerService.pushFlowMetrics(vEntry.deviceId(), builder.build());
+                }
+            } else if (event.type() == FlowRuleEvent.Type.RULE_REMOVED) {
+                if (frm.isVirtualIngressRule(event.subject())) {
+                    //FIXME confirm all physical rules are removed
+                    NetworkId networkId = frm.getVirtualNetworkId(event.subject());
+                    FlowEntry vEntry = getVirtualFlowEntry(event.subject());
+
+                    if (vEntry == null) {
+                        return;
+                    }
+
+                    frm.removeFlowEntry(networkId, vEntry.deviceId(), vEntry);
+                    frm.removeFlowRule(networkId, vEntry.deviceId(), vEntry);
+
+                    VirtualFlowRuleProviderService providerService =
+                            (VirtualFlowRuleProviderService) providerRegistryService
+                                    .getProviderService(networkId,
+                                                        VirtualFlowRuleProvider.class);
+                    providerService.flowRemoved(vEntry);
+                }
+            }
+        }
+
+        private FlowEntry getVirtualFlowEntry(FlowRule rule) {
+            FlowEntry entry = null;
+            for (FlowEntry fe :
+                    flowRuleService.getFlowEntries(rule.deviceId())) {
+                if (rule.exactMatch(fe)) {
+                    entry = fe;
+                }
+            }
+
+            if (entry != null) {
+                return virtualize(entry);
+            } else  {
+                return virtualize(new DefaultFlowEntry(rule,
+                                                       FlowEntry.FlowEntryState.PENDING_REMOVE));
+            }
+        }
+    }
+
+    private class InternalVirtualFlowRuleManager {
+        /** <Virtual Network ID, Virtual Device ID, Virtual Flow Rules>.*/
+        final Table<NetworkId, DeviceId, Set<FlowRule>> flowRuleTable
+                = HashBasedTable.create();
+
+        /** <Virtual Network ID, Virtual Device ID, Virtual Flow Entries>.*/
+        final Table<NetworkId, DeviceId, Set<FlowEntry>> flowEntryTable
+                = HashBasedTable.create();
+
+        /** <Physical Flow Rule, Virtual Network ID>.*/
+        final Map<FlowRule, NetworkId> ingressRuleMap = Maps.newHashMap();
+
+        /** <Physical Flow Rule, Virtual Virtual Flow Rule>.*/
+        final Map<FlowRule, FlowRule> virtualizationMap = Maps.newHashMap();
+
+        private Iterable<FlowRule> getFlowRules(NetworkId networkId,
+                                                DeviceId deviceId) {
+            return flowRuleTable.get(networkId, deviceId);
+        }
+
+        private Iterable<FlowEntry> getFlowEntries(NetworkId networkId,
+                                                   DeviceId deviceId) {
+            return flowEntryTable.get(networkId, deviceId);
+        }
+
+        private void addFlowRule(NetworkId networkId, DeviceId deviceId,
+                                 FlowRule flowRule) {
+            Set<FlowRule> set = flowRuleTable.get(networkId, deviceId);
+            if (set == null) {
+                set = Sets.newHashSet();
+                flowRuleTable.put(networkId, deviceId, set);
+            }
+            set.add(flowRule);
+        }
+
+        private void removeFlowRule(NetworkId networkId, DeviceId deviceId,
+                                    FlowRule flowRule) {
+            Set<FlowRule> set = flowRuleTable.get(networkId, deviceId);
+            if (set == null) {
+                return;
+            }
+            set.remove(flowRule);
+        }
+
+        private void addOrUpdateFlowEntry(NetworkId networkId, DeviceId deviceId,
+                                  FlowEntry flowEntry) {
+            Set<FlowEntry> set = flowEntryTable.get(networkId, deviceId);
+            if (set == null) {
+                set = Sets.newConcurrentHashSet();
+                flowEntryTable.put(networkId, deviceId, set);
+            }
+
+            //Replace old entry with new one
+            set.stream().filter(fe -> fe.exactMatch(flowEntry))
+                    .forEach(set::remove);
+            set.add(flowEntry);
+        }
+
+        private void removeFlowEntry(NetworkId networkId, DeviceId deviceId,
+                                     FlowEntry flowEntry) {
+            Set<FlowEntry> set = flowEntryTable.get(networkId, deviceId);
+            if (set == null) {
+                return;
+            }
+            set.remove(flowEntry);
+        }
+
+        private void addIngressRule(FlowRule virtualRule, FlowRule physicalRule,
+                                    NetworkId networkId) {
+            ingressRuleMap.put(physicalRule, networkId);
+            virtualizationMap.put(physicalRule, virtualRule);
+        }
+
+        private FlowRule getVirtualRule(FlowRule physicalRule) {
+                return virtualizationMap.get(physicalRule);
+        }
+
+        private void removeIngressRule(FlowRule physicalRule) {
+            ingressRuleMap.remove(physicalRule);
+            virtualizationMap.remove(physicalRule);
+        }
+
+        private Set<FlowRule> getAllPhysicalRule() {
+            return copyOf(virtualizationMap.keySet());
+        }
+
+        private NetworkId getVirtualNetworkId(FlowRule physicalRule) {
+            return ingressRuleMap.get(physicalRule);
+        }
+
+        /**
+         * Test the rule is the ingress rule for virtual rules.
+         *
+         * @param flowRule A flow rule from underlying data plane to be translated
+         * @return True when the rule is for ingress point for a virtual switch
+         */
+        private boolean isVirtualIngressRule(FlowRule flowRule) {
+            return ingressRuleMap.containsKey(flowRule);
+        }
+    }
+
+    private class DefaultInternalRoutingAlgorithm
+            implements InternalRoutingAlgorithm {
+
+        @Override
+        public Path findPath(ConnectPoint src, ConnectPoint dst) {
+            Set<Path> paths =
+                    topologyService.getPaths(topologyService.currentTopology(),
+                                             src.deviceId(),
+                                             dst.deviceId());
+
+            if (paths.isEmpty()) {
+                return null;
+            }
+
+            //TODO the logic find the best path
+            return (Path) paths.toArray()[0];
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualGroupProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualGroupProvider.java
new file mode 100644
index 0000000..7cc86cc
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualGroupProvider.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider to handle Group for virtual network.
+ */
+@Component(service = VirtualGroupProvider.class)
+public class DefaultVirtualGroupProvider extends AbstractVirtualProvider
+        implements VirtualGroupProvider {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualProviderRegistryService providerRegistryService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected GroupService groupService;
+
+    private InternalGroupEventListener internalGroupEventListener;
+
+    /**
+     * Creates a virtual provider with the supplied identifier.
+     */
+    public DefaultVirtualGroupProvider() {
+        super(new ProviderId("vnet-group", "org.onosproject.virtual.of-group"));
+    }
+
+    @Activate
+    public void activate() {
+        providerRegistryService.registerProvider(this);
+
+        internalGroupEventListener = new InternalGroupEventListener();
+        groupService.addListener(internalGroupEventListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        groupService.removeListener(internalGroupEventListener);
+        providerRegistryService.unregisterProvider(this);
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+    }
+
+    @Override
+    public void performGroupOperation(NetworkId networkId, DeviceId deviceId, GroupOperations groupOps) {
+        for (GroupOperation groupOperation: groupOps.operations()) {
+            switch (groupOperation.opType()) {
+                case ADD:
+                    //TODO: devirtualize + groupAdd
+                    log.info("Group Add is not supported, yet");
+                    break;
+                case MODIFY:
+                    //TODO: devirtualize + groupMod
+                    log.info("Group Modify is not supported, yet");
+                    break;
+                case DELETE:
+                    //TODO: devirtualize + groupDel
+                    log.info("Group Delete is not supported, yet");
+                    break;
+                default:
+                    log.error("Unsupported Group operation");
+                    return;
+            }
+        }
+    }
+
+    private class InternalGroupEventListener implements GroupListener {
+        @Override
+        public void event(GroupEvent event) {
+            switch (event.type()) {
+                //TODO: virtualize + notify to virtual provider service
+                case GROUP_ADD_REQUESTED:
+                case GROUP_UPDATE_REQUESTED:
+                case GROUP_REMOVE_REQUESTED:
+                case GROUP_ADDED:
+                case GROUP_UPDATED:
+                case GROUP_REMOVED:
+                case GROUP_ADD_FAILED:
+                case GROUP_UPDATE_FAILED:
+                case GROUP_REMOVE_FAILED:
+                case GROUP_BUCKET_FAILOVER:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualMeterProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualMeterProvider.java
new file mode 100644
index 0000000..26462ad
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualMeterProvider.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalCause;
+import com.google.common.cache.RemovalNotification;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterListener;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterOperations;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider to handle meters for virtual networks.
+ */
+@Component(service = VirtualMeterProvider.class)
+public class DefaultVirtualMeterProvider extends AbstractVirtualProvider
+        implements VirtualMeterProvider {
+
+    private final Logger log = getLogger(getClass());
+
+    static final long TIMEOUT = 30;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualProviderRegistryService providerRegistryService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected MeterService meterService;
+
+    private MeterListener internalMeterListener;
+    private Cache<Long, VirtualMeterOperation> pendingOperations;
+    private IdGenerator idGenerator;
+
+    @Activate
+    public void activate() {
+        providerRegistryService.registerProvider(this);
+        internalMeterListener = new InternalMeterListener();
+
+        idGenerator = getIdGenerator();
+
+        pendingOperations = CacheBuilder.newBuilder()
+                .expireAfterWrite(TIMEOUT, TimeUnit.SECONDS)
+                .removalListener(
+                        (RemovalNotification<Long, VirtualMeterOperation>
+                                 notification) -> {
+                    if (notification.getCause() == RemovalCause.EXPIRED) {
+                        NetworkId networkId = notification.getValue().networkId();
+                        MeterOperation op = notification.getValue().operation();
+
+                        VirtualMeterProviderService providerService =
+                                (VirtualMeterProviderService) providerRegistryService
+                                        .getProviderService(networkId,
+                                                            VirtualMeterProvider.class);
+
+                        providerService.meterOperationFailed(op,
+                                                             MeterFailReason.TIMEOUT);
+                    }
+                }).build();
+
+        meterService.addListener(internalMeterListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        meterService.removeListener(internalMeterListener);
+        providerRegistryService.unregisterProvider(this);
+    }
+
+    /**
+     * Creates a provider with the identifier.
+     */
+    public DefaultVirtualMeterProvider() {
+        super(new ProviderId("vnet-meter",
+                             "org.onosproject.virtual.vnet-meter"));
+    }
+
+    @Override
+    public void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+                                      MeterOperations meterOps) {
+        meterOps.operations().forEach(op -> performOperation(networkId, deviceId, op));
+    }
+
+    @Override
+    public void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+                                      MeterOperation meterOp) {
+        performOperation(networkId, deviceId, meterOp);
+    }
+
+    private void performOperation(NetworkId networkId, DeviceId deviceId,
+                                  MeterOperation op) {
+
+        VirtualMeterOperation vOp = new VirtualMeterOperation(networkId, op);
+        pendingOperations.put(idGenerator.getNewId(), vOp);
+
+        switch (op.type()) {
+            case ADD:
+                //TODO: devirtualize + submit
+                break;
+            case REMOVE:
+                //TODO: devirtualize + withdraw
+                break;
+            case MODIFY:
+                //TODO: devitualize + withdraw and submit
+                break;
+            default:
+                log.warn("Unknown Meter command {}; not sending anything",
+                         op.type());
+                VirtualMeterProviderService providerService =
+                        (VirtualMeterProviderService) providerRegistryService
+                                .getProviderService(networkId,
+                                                    VirtualMeterProvider.class);
+                providerService.meterOperationFailed(op,
+                                                     MeterFailReason.UNKNOWN_COMMAND);
+        }
+
+    }
+
+    /**
+     * De-virtualizes a meter operation.
+     * It takes a virtual meter operation, and translate it to a physical meter operation.
+     *
+     * @param networkId a virtual network identifier
+     * @param deviceId a virtual network device identifier
+     * @param meterOps a meter operation to be de-virtualized
+     * @return de-virtualized meter operation
+     */
+    private VirtualMeterOperation devirtualize(NetworkId networkId,
+                                      DeviceId deviceId,
+                                      MeterOperation meterOps) {
+        return null;
+    }
+
+    /**
+     * Virtualizes meter.
+     * This translates meter events for virtual networks before delivering them.
+     *
+     * @param meter
+     * @return
+     */
+    private Meter virtualize(Meter meter) {
+        return  null;
+    }
+
+
+    private class InternalMeterListener implements MeterListener {
+        @Override
+        public void event(MeterEvent event) {
+            //TODO: virtualize + notify event to meter provider service
+            //Is it enough to enable virtual network provider?
+            switch (event.type()) {
+                case METER_ADD_REQ:
+                    break;
+                case METER_REM_REQ:
+                    break;
+                case METER_ADDED:
+                    break;
+                case METER_REMOVED:
+                    break;
+                default:
+                    log.warn("Unknown meter event {}", event.type());
+            }
+        }
+    }
+
+    /**
+     * A class to hold a network identifier and a meter operation.
+     * This class is designed to be used only in virtual network meter provider.
+     */
+    private final class VirtualMeterOperation {
+        private NetworkId networkId;
+        private MeterOperation op;
+
+        private VirtualMeterOperation(NetworkId networkId, MeterOperation op) {
+            this.networkId = networkId;
+            this.op = op;
+        }
+
+        private NetworkId networkId() {
+            return networkId;
+        }
+
+        private MeterOperation operation() {
+            return this.op;
+        }
+    }
+
+    /**
+     * A class to hold a network identifier and a meter.
+     * This class is designed to be used in only virtual network meter provider.
+     */
+    private final class VirtualMeter {
+        private NetworkId networkId;
+        private Meter meter;
+
+        private VirtualMeter(NetworkId networkId, Meter meter) {
+            this.networkId = networkId;
+            this.meter = meter;
+        }
+
+        private NetworkId networkId() {
+            return this.networkId;
+        }
+
+        private Meter meter() {
+            return this.meter;
+        }
+    }
+
+    /**
+     * Id generator for virtual meters to guarantee the uniqueness of its identifier
+     * among multiple virtual network meters.
+     *
+     * @return an ID generator
+     */
+    private IdGenerator getIdGenerator() {
+        return new IdGenerator() {
+            private AtomicLong counter = new AtomicLong(0);
+
+            @Override
+            public long getNewId() {
+                return counter.getAndIncrement();
+            }
+        };
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualNetworkProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualNetworkProvider.java
new file mode 100644
index 0000000..3860dee
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualNetworkProvider.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import org.onosproject.incubator.net.virtual.DefaultVirtualLink;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyListener;
+import org.onosproject.net.topology.TopologyService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Virtual network topology provider.
+ */
+@Component(service = VirtualNetworkProvider.class)
+public class DefaultVirtualNetworkProvider
+        extends AbstractProvider implements VirtualNetworkProvider {
+
+    private final Logger log = getLogger(DefaultVirtualNetworkProvider.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualNetworkProviderRegistry providerRegistry;
+
+    private VirtualNetworkProviderService providerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected TopologyService topologyService;
+
+    protected TopologyListener topologyListener = new InternalTopologyListener();
+
+    private ExecutorService executor;
+
+    /**
+     * Default constructor.
+     */
+    public DefaultVirtualNetworkProvider() {
+        super(DefaultVirtualLink.PID);
+    }
+
+    @Activate
+    public void activate() {
+        executor = newSingleThreadExecutor(groupedThreads("onos/vnet", "provider", log));
+        providerService = providerRegistry.register(this);
+        topologyService.addListener(topologyListener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        topologyService.removeListener(topologyListener);
+        executor.shutdownNow();
+        executor = null;
+        providerRegistry.unregister(this);
+        providerService = null;
+        log.info("Stopped");
+    }
+
+    @Override
+    public boolean isTraversable(ConnectPoint src, ConnectPoint dst) {
+        final boolean[] foundSrc = new boolean[1];
+        final boolean[] foundDst = new boolean[1];
+        Topology topology = topologyService.currentTopology();
+        Set<Path> paths = topologyService.getPaths(topology, src.deviceId(), dst.deviceId());
+        paths.forEach(path -> {
+            foundDst[0] = false;
+            foundSrc[0] = false;
+            // Traverse the links in each path to determine if both the src and dst connection
+            // point are in the path, if so then this src/dst pair are traversable.
+            path.links().forEach(link -> {
+                if (link.src().equals(src)) {
+                    foundSrc[0] = true;
+                }
+                if (link.dst().equals(dst)) {
+                    foundDst[0] = true;
+                }
+            });
+            if (foundSrc[0] && foundDst[0]) {
+                return;
+            }
+        });
+        return foundSrc[0] && foundDst[0];
+    }
+
+    /**
+     * Returns a set of set of interconnected connect points in the default topology.
+     * The inner set represents the interconnected connect points, and the outerset
+     * represents separate clusters.
+     *
+     * @param topology the default topology
+     * @return set of set of interconnected connect points.
+     */
+    public Set<Set<ConnectPoint>> getConnectPoints(Topology topology) {
+        Set<Set<ConnectPoint>> clusters = new HashSet<>();
+        Set<TopologyCluster> topologyClusters = topologyService.getClusters(topology);
+        topologyClusters.forEach(topologyCluster -> {
+            Set<ConnectPoint> connectPointSet = new HashSet<>();
+            Set<Link> clusterLinks =
+                    topologyService.getClusterLinks(topology, topologyCluster);
+            clusterLinks.forEach(link -> {
+                connectPointSet.add(link.src());
+                connectPointSet.add(link.dst());
+            });
+            if (!connectPointSet.isEmpty()) {
+                clusters.add(connectPointSet);
+            }
+        });
+        return clusters;
+    }
+
+    /**
+     * Topology event listener.
+     */
+    private class InternalTopologyListener implements TopologyListener {
+        @Override
+        public void event(TopologyEvent event) {
+            // Perform processing off the listener thread.
+            executor.submit(() -> providerService
+                    .topologyChanged(getConnectPoints(event.subject())));
+        }
+
+        @Override
+        public boolean isRelevant(TopologyEvent event) {
+            return event.type() == TopologyEvent.Type.TOPOLOGY_CHANGED &&
+                    event.reasons().stream().anyMatch(reason -> reason instanceof LinkEvent);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketContext.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketContext.java
new file mode 100644
index 0000000..014f436
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketContext.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualPacketContext;
+import org.onosproject.net.packet.DefaultPacketContext;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+
+/**
+ *Default implementation of a virtual packet context.
+ */
+public class DefaultVirtualPacketContext extends DefaultPacketContext
+        implements VirtualPacketContext {
+
+    private NetworkId networkId;
+    private DefaultVirtualPacketProvider dvpp;
+
+    /**
+     * Creates a new packet context.
+     *
+     * @param time   creation time
+     * @param inPkt  inbound packet
+     * @param outPkt outbound packet
+     * @param block  whether the context is blocked or not
+     * @param networkId virtual network ID where this context is handled
+     * @param dvpp  pointer to default virtual packet provider
+     */
+
+    protected DefaultVirtualPacketContext(long time, InboundPacket inPkt,
+                                          OutboundPacket outPkt, boolean block,
+                                          NetworkId networkId,
+                                          DefaultVirtualPacketProvider dvpp) {
+        super(time, inPkt, outPkt, block);
+
+        this.networkId = networkId;
+        this.dvpp = dvpp;
+    }
+
+    @Override
+    public void send() {
+        if (!this.block()) {
+            dvpp.send(this);
+        }
+    }
+
+    @Override
+    public NetworkId networkId() {
+        return networkId;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProvider.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProvider.java
new file mode 100644
index 0000000..9048954
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProvider.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.Sets;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.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.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualPacketContext;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(service = VirtualPacketProvider.class)
+public class DefaultVirtualPacketProvider extends AbstractVirtualProvider
+        implements VirtualPacketProvider {
+
+    private static final int PACKET_PROCESSOR_PRIORITY = 1;
+    private static final PacketPriority VIRTUAL_PACKET_PRIORITY = PacketPriority.REACTIVE;
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualNetworkAdminService vnaService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualProviderRegistryService providerRegistryService;
+
+    private final VirtualNetworkListener virtualNetListener = new InternalVirtualNetworkListener();
+
+    private InternalPacketProcessor processor = null;
+
+    private Set<NetworkId> networkIdSet = Sets.newConcurrentHashSet();
+
+    private ApplicationId appId;
+
+    /**
+     * Creates a provider with the supplied identifier.
+     */
+    public DefaultVirtualPacketProvider() {
+        super(new ProviderId("virtual-packet", "org.onosproject.virtual.virtual-packet"));
+    }
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onosproject.virtual.virtual-packet");
+        providerRegistryService.registerProvider(this);
+        vnaService.addListener(virtualNetListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+
+        providerRegistryService.unregisterProvider(this);
+        vnaService.removeListener(virtualNetListener);
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+    }
+
+
+    @Override
+    public void emit(NetworkId networkId, OutboundPacket packet) {
+       devirtualize(networkId, packet)
+               .forEach(outboundPacket -> packetService.emit(outboundPacket));
+    }
+
+    /**
+     * Just for test.
+     */
+    protected void startPacketHandling() {
+        processor = new InternalPacketProcessor();
+        packetService.addProcessor(processor, PACKET_PROCESSOR_PRIORITY);
+    }
+
+    /**
+     * Send the outbound packet of a virtual context.
+     * This method is designed to support Context's send() method that invoked
+     * by applications.
+     * See {@link org.onosproject.net.packet.PacketContext}
+     *
+     * @param virtualPacketContext virtual packet context
+     */
+    protected void send(VirtualPacketContext virtualPacketContext) {
+        devirtualizeContext(virtualPacketContext)
+                .forEach(outboundPacket -> packetService.emit(outboundPacket));
+    }
+
+    /**
+     * Translate the requested physical PacketContext into a virtual PacketContext.
+     * See {@link org.onosproject.net.packet.PacketContext}
+     *
+     * @param context A physical PacketContext be translated
+     * @return A translated virtual PacketContext
+     */
+    private VirtualPacketContext virtualize(PacketContext context) {
+
+        VirtualPort vPort = getMappedVirtualPort(context.inPacket().receivedFrom());
+
+        if (vPort != null) {
+            ConnectPoint cp = new ConnectPoint(vPort.element().id(),
+                                               vPort.number());
+
+            Ethernet eth = context.inPacket().parsed();
+            eth.setVlanID(Ethernet.VLAN_UNTAGGED);
+
+            InboundPacket inPacket =
+                    new DefaultInboundPacket(cp, eth,
+                                             ByteBuffer.wrap(eth.serialize()));
+
+            DefaultOutboundPacket outPkt =
+                    new DefaultOutboundPacket(cp.deviceId(),
+                                              DefaultTrafficTreatment.builder().build(),
+                                              ByteBuffer.wrap(eth.serialize()));
+
+            VirtualPacketContext vContext =
+                    new DefaultVirtualPacketContext(context.time(), inPacket, outPkt,
+                                             false, vPort.networkId(),
+                                             this);
+
+            return vContext;
+        } else {
+            return null;
+        }
+
+    }
+
+    /**
+     * Find the corresponding virtual port with the physical port.
+     *
+     * @param cp the connect point for the physical network
+     * @return a virtual port
+     */
+    private VirtualPort getMappedVirtualPort(ConnectPoint cp) {
+        Set<TenantId> tIds = vnaService.getTenantIds();
+
+        Set<VirtualNetwork> vNetworks = new HashSet<>();
+        tIds.forEach(tid -> vNetworks.addAll(vnaService.getVirtualNetworks(tid)));
+
+        for (VirtualNetwork vNet : vNetworks) {
+            Set<VirtualDevice> vDevices = vnaService.getVirtualDevices(vNet.id());
+
+            Set<VirtualPort> vPorts = new HashSet<>();
+            vDevices.forEach(dev -> vPorts
+                    .addAll(vnaService.getVirtualPorts(dev.networkId(), dev.id())));
+
+            VirtualPort vPort = vPorts.stream()
+                    .filter(vp -> vp.realizedBy().equals(cp))
+                    .findFirst().orElse(null);
+
+            if (vPort != null) {
+                return vPort;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Translate the requested virtual outbound packet into
+     * a set of physical OutboundPacket.
+     * See {@link org.onosproject.net.packet.OutboundPacket}
+     *
+     * @param packet an OutboundPacket to be translated
+     * @return a set of de-virtualized (physical) OutboundPacket
+     */
+    private Set<OutboundPacket> devirtualize(NetworkId networkId, OutboundPacket packet) {
+        Set<OutboundPacket> outboundPackets = new HashSet<>();
+        Set<VirtualPort> vPorts = vnaService
+                .getVirtualPorts(networkId, packet.sendThrough());
+
+        TrafficTreatment.Builder commonTreatmentBuilder
+                = DefaultTrafficTreatment.builder();
+        packet.treatment().allInstructions().stream()
+                .filter(i -> i.type() != Instruction.Type.OUTPUT)
+                .forEach(i -> commonTreatmentBuilder.add(i));
+        TrafficTreatment commonTreatment = commonTreatmentBuilder.build();
+
+        PortNumber vOutPortNum = packet.treatment().allInstructions().stream()
+                .filter(i -> i.type() == Instruction.Type.OUTPUT)
+                .map(i -> ((Instructions.OutputInstruction) i).port())
+                .findFirst().get();
+
+        if (!vOutPortNum.isLogical()) {
+            Optional<ConnectPoint> optionalCpOut = vPorts.stream()
+                    .filter(v -> v.number().equals(vOutPortNum))
+                    .map(v -> v.realizedBy())
+                    .findFirst();
+            if (!optionalCpOut.isPresent()) {
+                log.warn("Port {} is not realized yet, in Network {}, Device {}",
+                        vOutPortNum, networkId, packet.sendThrough());
+                return outboundPackets;
+            }
+            ConnectPoint egressPoint = optionalCpOut.get();
+
+            TrafficTreatment treatment = DefaultTrafficTreatment
+                    .builder(commonTreatment)
+                    .setOutput(egressPoint.port()).build();
+
+            OutboundPacket outboundPacket = new DefaultOutboundPacket(
+                    egressPoint.deviceId(), treatment, packet.data());
+            outboundPackets.add(outboundPacket);
+        } else {
+            if (vOutPortNum == PortNumber.FLOOD) {
+                for (VirtualPort outPort : vPorts) {
+                    ConnectPoint cpOut = outPort.realizedBy();
+                    if (cpOut != null) {
+                        TrafficTreatment treatment = DefaultTrafficTreatment
+                                .builder(commonTreatment)
+                                .setOutput(cpOut.port()).build();
+                        OutboundPacket outboundPacket = new DefaultOutboundPacket(
+                                cpOut.deviceId(), treatment, packet.data());
+                        outboundPackets.add(outboundPacket);
+                    } else {
+                        log.warn("Port {} is not realized yet, in Network {}, Device {}",
+                                outPort.number(), networkId, packet.sendThrough());
+                    }
+                }
+            }
+        }
+
+        return outboundPackets;
+    }
+
+    /**
+     * Translate the requested virtual packet context into
+     * a set of physical outbound packets.
+     *
+     * @param context A handled virtual packet context
+     */
+    private Set<OutboundPacket> devirtualizeContext(VirtualPacketContext context) {
+
+        Set<OutboundPacket> outboundPackets = new HashSet<>();
+
+        NetworkId networkId = context.networkId();
+        TrafficTreatment vTreatment = context.treatmentBuilder().build();
+        DeviceId sendThrough = context.outPacket().sendThrough();
+
+        Set<VirtualPort> vPorts = vnaService
+                .getVirtualPorts(networkId, sendThrough);
+
+        PortNumber vOutPortNum = vTreatment.allInstructions().stream()
+                .filter(i -> i.type() == Instruction.Type.OUTPUT)
+                .map(i -> ((Instructions.OutputInstruction) i).port())
+                .findFirst().get();
+
+        TrafficTreatment.Builder commonTreatmentBuilder
+                = DefaultTrafficTreatment.builder();
+        vTreatment.allInstructions().stream()
+                .filter(i -> i.type() != Instruction.Type.OUTPUT)
+                .forEach(i -> commonTreatmentBuilder.add(i));
+        TrafficTreatment commonTreatment = commonTreatmentBuilder.build();
+
+        if (!vOutPortNum.isLogical()) {
+            Optional<ConnectPoint> optionalCpOut = vPorts.stream()
+                    .filter(v -> v.number().equals(vOutPortNum))
+                    .map(v -> v.realizedBy())
+                    .findFirst();
+            if (!optionalCpOut.isPresent()) {
+                log.warn("Port {} is not realized yet, in Network {}, Device {}",
+                        vOutPortNum, networkId, sendThrough);
+                return outboundPackets;
+            }
+            ConnectPoint egressPoint = optionalCpOut.get();
+
+            TrafficTreatment treatment = DefaultTrafficTreatment
+                    .builder(commonTreatment)
+                    .setOutput(egressPoint.port()).build();
+
+            OutboundPacket outboundPacket = new DefaultOutboundPacket(
+                    egressPoint.deviceId(), treatment, context.outPacket().data());
+            outboundPackets.add(outboundPacket);
+        } else {
+            if (vOutPortNum == PortNumber.FLOOD) {
+                Set<VirtualPort> outPorts = vPorts.stream()
+                        .filter(vp -> !vp.number().isLogical())
+                        .filter(vp -> vp.number() !=
+                                context.inPacket().receivedFrom().port())
+                        .collect(Collectors.toSet());
+
+                for (VirtualPort outPort : outPorts) {
+                    ConnectPoint cpOut = outPort.realizedBy();
+                    if (cpOut != null) {
+                        TrafficTreatment treatment = DefaultTrafficTreatment
+                                .builder(commonTreatment)
+                                .setOutput(cpOut.port()).build();
+                        OutboundPacket outboundPacket = new DefaultOutboundPacket(
+                                cpOut.deviceId(), treatment, context.outPacket().data());
+                        outboundPackets.add(outboundPacket);
+                    } else {
+                        log.warn("Port {} is not realized yet, in Network {}, Device {}",
+                                outPort.number(), networkId, sendThrough);
+                    }
+                }
+            }
+        }
+        return outboundPackets;
+    }
+
+    private final class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+            VirtualPacketContext vContexts = virtualize(context);
+
+            if (vContexts == null) {
+                return;
+            }
+
+            VirtualPacketProviderService service =
+                    (VirtualPacketProviderService) providerRegistryService
+                            .getProviderService(vContexts.networkId(),
+                                                VirtualPacketProvider.class);
+            if (service != null) {
+                service.processPacket(vContexts);
+            }
+        }
+    }
+
+    private class InternalVirtualNetworkListener implements VirtualNetworkListener {
+
+        @Override
+        public void event(VirtualNetworkEvent event) {
+            switch (event.type()) {
+                case NETWORK_ADDED:
+                    if (networkIdSet.isEmpty()) {
+                        processor = new InternalPacketProcessor();
+                        packetService.addProcessor(processor, PACKET_PROCESSOR_PRIORITY);
+                        log.info("Packet processor {} for virtual network is added.", processor.getClass().getName());
+                    }
+                    networkIdSet.add(event.subject());
+                    break;
+
+                case NETWORK_REMOVED:
+                    networkIdSet.remove(event.subject());
+                    if (networkIdSet.isEmpty()) {
+                        packetService.removeProcessor(processor);
+                        log.info("Packet processor {} for virtual network is removed.", processor.getClass().getName());
+                        processor = null;
+                    }
+                    break;
+
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManager.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManager.java
new file mode 100644
index 0000000..ebd87ea
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManager.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.VirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Implementation of the virtual provider registry and providerService registry service.
+ */
+@Component(service = VirtualProviderRegistryService.class)
+public class VirtualProviderManager implements VirtualProviderRegistryService {
+
+    private final Map<ProviderId, VirtualProvider> providers = new HashMap<>();
+    private final Map<ProviderId, VirtualProviderService> servicesWithProvider = new HashMap<>();
+    private final Map<String, VirtualProvider> providersByScheme = new HashMap<>();
+    private final Map<NetworkId, Set<VirtualProviderService>> servicesByNetwork = new HashMap<>();
+    private static Logger log = LoggerFactory.getLogger(VirtualProviderManager.class);
+
+    @Override
+    public synchronized void registerProvider(VirtualProvider virtualProvider) {
+        checkNotNull(virtualProvider, "Provider cannot be null");
+        checkState(!providers.containsKey(virtualProvider.id()),
+                   "Provider %s already registered", virtualProvider.id());
+
+        // If the provider is a primary one, check for a conflict.
+        ProviderId pid = virtualProvider.id();
+        checkState(pid.isAncillary() || !providersByScheme.containsKey(pid.scheme()),
+                   "A primary provider with id %s is already registered",
+                   providersByScheme.get(pid.scheme()));
+
+        providers.put(virtualProvider.id(), virtualProvider);
+
+        // Register the provider by URI scheme only if it is not ancillary.
+        if (!pid.isAncillary()) {
+            providersByScheme.put(pid.scheme(), virtualProvider);
+        }
+    }
+
+    @Override
+    public synchronized void unregisterProvider(VirtualProvider virtualProvider) {
+        checkNotNull(virtualProvider, "Provider cannot be null");
+
+        //TODO: invalidate provider services which subscribe the provider
+        providers.remove(virtualProvider.id());
+
+        if (!virtualProvider.id().isAncillary()) {
+            providersByScheme.remove(virtualProvider.id().scheme());
+        }
+    }
+
+    @Override
+    public synchronized void
+    registerProviderService(NetworkId networkId,
+                            VirtualProviderService virtualProviderService) {
+        Set<VirtualProviderService> services =
+                servicesByNetwork.computeIfAbsent(networkId, k -> new HashSet<>());
+
+        services.add(virtualProviderService);
+    }
+
+    @Override
+    public synchronized void
+    unregisterProviderService(NetworkId networkId,
+                              VirtualProviderService virtualProviderService) {
+        Set<VirtualProviderService> services = servicesByNetwork.get(networkId);
+
+        if (services != null) {
+            services.remove(virtualProviderService);
+        }
+    }
+
+    @Override
+    public synchronized Set<ProviderId> getProviders() {
+        return ImmutableSet.copyOf(providers.keySet());
+    }
+
+    @Override
+    public Set<ProviderId> getProvidersByService(VirtualProviderService
+                                                             virtualProviderService) {
+        Class clazz = getProviderClass(virtualProviderService);
+
+        return ImmutableSet.copyOf(providers.values().stream()
+                                           .filter(clazz::isInstance)
+                                           .map(VirtualProvider::id)
+                                           .collect(Collectors.toSet()));
+    }
+
+    @Override
+    public synchronized VirtualProvider getProvider(ProviderId providerId) {
+        return providers.get(providerId);
+    }
+
+    @Override
+    public synchronized VirtualProvider getProvider(DeviceId deviceId) {
+        return providersByScheme.get(deviceId.uri().getScheme());
+    }
+
+    @Override
+    public synchronized VirtualProvider getProvider(String scheme) {
+        return providersByScheme.get(scheme);
+    }
+
+    @Override
+    public synchronized VirtualProviderService
+    getProviderService(NetworkId networkId, Class<? extends VirtualProvider> providerClass) {
+        Set<VirtualProviderService> services = servicesByNetwork.get(networkId);
+
+        if (services == null) {
+            return null;
+        }
+
+        return services.stream()
+                .filter(s -> getProviderClass(s).equals(providerClass))
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Returns the class type of parameter type.
+     * More specifically, it returns the class type of provider service's provider type.
+     *
+     * @param service a virtual provider service
+     * @return the class type of provider service of the service
+     */
+    private Class getProviderClass(VirtualProviderService service) {
+       String className = service.getClass().getGenericSuperclass().getTypeName();
+       String pramType = className.split("<")[1].split(">")[0];
+
+        try {
+            return Class.forName(pramType);
+        } catch (ClassNotFoundException e) {
+            log.warn("getProviderClass()", e);
+        }
+
+        return null;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/package-info.java
new file mode 100644
index 0000000..1d9646f
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/impl/provider/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Network virtualization provider implementations.
+ */
+package org.onosproject.incubator.net.virtual.impl.provider;
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/TenantWebResource.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/TenantWebResource.java
new file mode 100644
index 0000000..768c3b9
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/TenantWebResource.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.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;
+
+import static org.onlab.util.Tools.readTreeFromStream;
+
+/**
+ * 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
+    private UriInfo uriInfo;
+
+    private final VirtualNetworkAdminService vnetAdminService = get(VirtualNetworkAdminService.class);
+
+    /**
+     * Returns all tenant identifiers.
+     *
+     * @return 200 OK with set of tenant identifiers
+     * @onos.rsModel TenantIds
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getVirtualNetworkTenantIds() {
+        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 204 NO CONTENT
+     */
+    @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.noContent().build();
+    }
+
+    /**
+     * Gets the tenant identifier from the JSON stream.
+     *
+     * @param stream TenantId JSON stream
+     * @return TenantId
+     * @throws IOException if unable to parse the request
+     */
+    private TenantId getTenantIdFromJsonStream(InputStream stream) throws IOException {
+        ObjectNode jsonTree = readTreeFromStream(mapper(), 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 virtual network administration service
+     * @param tidIn        tenant identifier
+     * @return TenantId
+     */
+    protected static TenantId getExistingTenantId(VirtualNetworkAdminService vnetAdminSvc,
+                                                TenantId tidIn) {
+        return vnetAdminSvc
+                .getTenantIds()
+                .stream()
+                .filter(tenantId -> tenantId.equals(tidIn))
+                .findFirst()
+                .orElseThrow(() -> new ItemNotFoundException(TENANTID_NOT_FOUND));
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebApplication.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebApplication.java
new file mode 100644
index 0000000..4a80dbe
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebApplication.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.rest;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * Virtual network REST APIs web application.
+ */
+public class VirtualNetworkWebApplication extends AbstractWebApplication {
+    @Override
+    public Set<Class<?>> getClasses() {
+        return getClasses(
+                TenantWebResource.class,
+                VirtualNetworkWebResource.class
+        );
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResource.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResource.java
new file mode 100644
index 0000000..e8696c3
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResource.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+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.DeviceId;
+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;
+
+import static org.onlab.util.Tools.readTreeFromStream;
+
+/**
+ * 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
+    private UriInfo uriInfo;
+
+    // VirtualNetwork
+
+    /**
+     * Returns all virtual networks.
+     *
+     * @return 200 OK with set of virtual networks
+     * @onos.rsModel VirtualNetworks
+     */
+    @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();
+    }
+
+    /**
+     * Returns the virtual networks with the specified tenant identifier.
+     *
+     * @param tenantId tenant identifier
+     * @return 200 OK with a virtual network, 404 not found
+     * @onos.rsModel VirtualNetworks
+     */
+    @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 tenant identifier 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 204 NO CONTENT
+     */
+    @DELETE
+    @Path("{networkId}")
+    public Response removeVirtualNetwork(@PathParam("networkId") long networkId) {
+        NetworkId nid = NetworkId.networkId(networkId);
+        vnetAdminService.removeVirtualNetwork(nid);
+        return Response.noContent().build();
+    }
+
+    // VirtualDevice
+
+    /**
+     * Returns all virtual network devices in a virtual network.
+     *
+     * @param networkId network identifier
+     * @return 200 OK with set of virtual devices, 404 not found
+     * @onos.rsModel VirtualDevices
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/devices")
+    public Response getVirtualDevices(@PathParam("networkId") long networkId) {
+        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 = readTreeFromStream(mapper(), stream);
+            final VirtualDevice vdevReq = codec(VirtualDevice.class).decode(jsonTree, this);
+            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(specifiedNetworkId.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 204 NO CONTENT
+     */
+    @DELETE
+    @Path("{networkId}/devices/{deviceId}")
+    public Response removeVirtualDevice(@PathParam("networkId") long networkId,
+                                        @PathParam("deviceId") String deviceId) {
+        NetworkId nid = NetworkId.networkId(networkId);
+        DeviceId did = DeviceId.deviceId(deviceId);
+        vnetAdminService.removeVirtualDevice(nid, did);
+        return Response.noContent().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 with set of virtual ports, 404 not found
+     * @onos.rsModel VirtualPorts
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/devices/{deviceId}/ports")
+    public Response getVirtualPorts(@PathParam("networkId") long networkId,
+                                    @PathParam("deviceId") String deviceId) {
+        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 port 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 = readTreeFromStream(mapper(), 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);
+
+            ConnectPoint realizedBy = new ConnectPoint(DeviceId.deviceId(specifiedPhysDeviceId.asText()),
+                                              PortNumber.portNumber(specifiedPhysPortNum.asText()));
+            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 204 NO CONTENT
+     */
+    @DELETE
+    @Path("{networkId}/devices/{deviceId}/ports/{portNum}")
+    public Response removeVirtualPort(@PathParam("networkId") long networkId,
+                                      @PathParam("deviceId") String deviceId,
+                                      @PathParam("portNum") long portNum) {
+        NetworkId nid = NetworkId.networkId(networkId);
+        vnetAdminService.removeVirtualPort(nid, DeviceId.deviceId(deviceId),
+                                           PortNumber.portNumber(portNum));
+        return Response.noContent().build();
+    }
+
+    // VirtualLink
+
+    /**
+     * Returns all virtual network links in a virtual network.
+     *
+     * @param networkId network identifier
+     * @return 200 OK with set of virtual network links
+     * @onos.rsModel VirtualLinks
+     */
+    @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 link 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 = readTreeFromStream(mapper(), 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.createVirtualLink(vlinkReq.networkId(),
+                                               vlinkReq.src(), vlinkReq.dst());
+            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    virtual link JSON stream
+     * @return 204 NO CONTENT
+     * @onos.rsModel VirtualLink
+     */
+    @DELETE
+    @Path("{networkId}/links")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response removeVirtualLink(@PathParam("networkId") long networkId,
+                                      InputStream stream) {
+        try {
+            ObjectNode jsonTree = readTreeFromStream(mapper(), 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.noContent().build();
+    }
+
+    /**
+     * Returns all virtual network hosts in a virtual network.
+     *
+     * @param networkId network identifier
+     * @return 200 OK with set of virtual network hosts
+     * @onos.rsModel VirtualHosts
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{networkId}/hosts")
+    public Response getVirtualHosts(@PathParam("networkId") long networkId) {
+        NetworkId nid = NetworkId.networkId(networkId);
+        Set<VirtualHost> vhosts = vnetService.getVirtualHosts(nid);
+        return ok(encodeArray(VirtualHost.class, "hosts", vhosts)).build();
+    }
+
+    /**
+     * Creates a virtual network host from the JSON input stream.
+     *
+     * @param networkId network identifier
+     * @param stream    virtual host JSON stream
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel VirtualHostPut
+     */
+    @POST
+    @Path("{networkId}/hosts")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createVirtualHost(@PathParam("networkId") long networkId,
+                                      InputStream stream) {
+        try {
+            ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+            JsonNode specifiedNetworkId = jsonTree.get("networkId");
+            if (specifiedNetworkId == null || specifiedNetworkId.asLong() != (networkId)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+            }
+            final VirtualHost vhostReq = codec(VirtualHost.class).decode(jsonTree, this);
+            vnetAdminService.createVirtualHost(vhostReq.networkId(), vhostReq.id(),
+                                               vhostReq.mac(), vhostReq.vlan(),
+                                               vhostReq.location(), vhostReq.ipAddresses());
+            UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                    .path("vnets").path(specifiedNetworkId.asText())
+                    .path("hosts");
+            return Response
+                    .created(locationBuilder.build())
+                    .build();
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Removes the virtual network host from the JSON input stream.
+     *
+     * @param networkId network identifier
+     * @param stream    virtual host JSON stream
+     * @return 204 NO CONTENT
+     * @onos.rsModel VirtualHost
+     */
+    @DELETE
+    @Path("{networkId}/hosts")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response removeVirtualHost(@PathParam("networkId") long networkId,
+                                      InputStream stream) {
+        try {
+            ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+            JsonNode specifiedNetworkId = jsonTree.get("networkId");
+            if (specifiedNetworkId != null &&
+                    specifiedNetworkId.asLong() != (networkId)) {
+                throw new IllegalArgumentException(INVALID_FIELD + "networkId");
+            }
+            final VirtualHost vhostReq = codec(VirtualHost.class).decode(jsonTree, this);
+            vnetAdminService.removeVirtualHost(vhostReq.networkId(), vhostReq.id());
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return Response.noContent().build();
+    }
+
+    /**
+     * Get the tenant identifier from the JSON stream.
+     *
+     * @param stream        TenantId JSON stream
+     * @param jsonFieldName field name
+     * @return JsonNode
+     * @throws IOException if unable to parse the request
+     */
+    private JsonNode getFromJsonStream(InputStream stream, String jsonFieldName) throws IOException {
+        ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+        JsonNode jsonNode = jsonTree.get(jsonFieldName);
+
+        if (jsonNode == null) {
+            throw new IllegalArgumentException(MISSING_FIELD + jsonFieldName);
+        }
+        return jsonNode;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/package-info.java
new file mode 100644
index 0000000..0b8a711
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/rest/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * REST API of the virtual network subsystem.
+ */
+package org.onosproject.incubator.net.virtual.rest;
\ No newline at end of file
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/AbstractVirtualStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/AbstractVirtualStore.java
new file mode 100644
index 0000000..73f5bdb
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/AbstractVirtualStore.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Maps;
+import org.onosproject.event.Event;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualStore;
+import org.onosproject.store.StoreDelegate;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base implementation of a virtual store.
+ */
+public class AbstractVirtualStore<E extends Event, D extends StoreDelegate<E>>
+        implements VirtualStore<E, D> {
+
+    protected Map<NetworkId, D> delegateMap = Maps.newConcurrentMap();
+
+    @Override
+    public void setDelegate(NetworkId networkId, D delegate) {
+        checkState(delegateMap.get(networkId) == null
+                           || delegateMap.get(networkId) == delegate,
+                   "Store delegate already set");
+
+        delegateMap.putIfAbsent(networkId, delegate);
+    }
+
+    @Override
+    public void unsetDelegate(NetworkId networkId, D delegate) {
+        if (delegateMap.get(networkId) == delegate) {
+            delegateMap.remove(networkId, delegate);
+        }
+    }
+
+    @Override
+    public boolean hasDelegate(NetworkId networkId) {
+        return delegateMap.get(networkId) != null;
+    }
+
+    /**
+     * Notifies the delegate with the specified event.
+     *
+     * @param networkId a virtual network identifier
+     * @param event event to delegate
+     */
+    protected void notifyDelegate(NetworkId networkId, E event) {
+        if (delegateMap.get(networkId) != null) {
+            delegateMap.get(networkId).notify(event);
+        }
+    }
+
+    /**
+     * Notifies the delegate with the specified list of events.
+     *
+     * @param networkId a virtual network identifier
+     * @param events list of events to delegate
+     */
+    protected void notifyDelegate(NetworkId networkId, List<E> events) {
+        for (E event: events) {
+            notifyDelegate(networkId, event);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/ConsistentVirtualDeviceMastershipStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/ConsistentVirtualDeviceMastershipStore.java
new file mode 100644
index 0000000..408ce9c
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/ConsistentVirtualDeviceMastershipStore.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.Leadership;
+import org.onosproject.cluster.LeadershipAdminService;
+import org.onosproject.cluster.LeadershipEvent;
+import org.onosproject.cluster.LeadershipEventListener;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.Serializer;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.SUSPENDED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true, enabled = false, service = VirtualNetworkMastershipStore.class)
+public class ConsistentVirtualDeviceMastershipStore
+        extends AbstractVirtualStore<MastershipEvent, MastershipStoreDelegate>
+        implements VirtualNetworkMastershipStore {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LeadershipAdminService leadershipAdminService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterCommunicationService clusterCommunicator;
+
+    private NodeId localNodeId;
+
+    private static final MessageSubject ROLE_RELINQUISH_SUBJECT =
+            new MessageSubject("virtual-mastership-store-device-role-relinquish");
+
+    private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN =
+            Pattern.compile("vnet:(.*),device:(.*)");
+
+    private ExecutorService eventHandler;
+    private ExecutorService messageHandlingExecutor;
+    private ScheduledExecutorService transferExecutor;
+    private final LeadershipEventListener leadershipEventListener =
+            new InternalDeviceMastershipEventListener();
+
+    private static final String NODE_ID_NULL = "Node ID cannot be null";
+    private static final String NETWORK_ID_NULL = "Network ID cannot be null";
+    private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+    private static final int WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS = 3000;
+
+    public static final Serializer SERIALIZER = Serializer.using(
+            KryoNamespace.newBuilder()
+                    .register(KryoNamespaces.API)
+                    .register(MastershipRole.class)
+                    .register(MastershipEvent.class)
+                    .register(MastershipEvent.Type.class)
+                    .register(VirtualDeviceId.class)
+                    .build("VirtualMastershipStore"));
+
+    @Activate
+    public void activate() {
+        eventHandler = Executors.newSingleThreadExecutor(
+                groupedThreads("onos/store/virtual/mastership", "event-handler", log));
+
+        messageHandlingExecutor =
+                Executors.newSingleThreadExecutor(
+                        groupedThreads("onos/store/virtual/mastership", "message-handler", log));
+        transferExecutor =
+                Executors.newSingleThreadScheduledExecutor(
+                        groupedThreads("onos/store/virtual/mastership", "mastership-transfer-executor", log));
+        clusterCommunicator.addSubscriber(ROLE_RELINQUISH_SUBJECT,
+                                          SERIALIZER::decode,
+                                          this::relinquishLocalRole,
+                                          SERIALIZER::encode,
+                                          messageHandlingExecutor);
+        localNodeId = clusterService.getLocalNode().id();
+        leadershipService.addListener(leadershipEventListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT);
+        leadershipService.removeListener(leadershipEventListener);
+        messageHandlingExecutor.shutdown();
+        transferExecutor.shutdown();
+        eventHandler.shutdown();
+        log.info("Stopped");
+    }
+
+    @Override
+    public CompletableFuture<MastershipRole> requestRole(NetworkId networkId,
+                                                         DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+        Leadership leadership = leadershipService.runForLeadership(leadershipTopic);
+        return CompletableFuture
+                .completedFuture(localNodeId.equals(leadership.leaderNodeId()) ?
+                                         MastershipRole.MASTER : MastershipRole.STANDBY);
+    }
+
+    @Override
+    public MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(nodeId != null, NODE_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+        Leadership leadership = leadershipService.getLeadership(leadershipTopic);
+        NodeId leader = leadership == null ? null : leadership.leaderNodeId();
+        List<NodeId> candidates = leadership == null ?
+                ImmutableList.of() : ImmutableList.copyOf(leadership.candidates());
+        return Objects.equal(nodeId, leader) ?
+                MastershipRole.MASTER : candidates.contains(nodeId) ?
+                MastershipRole.STANDBY : MastershipRole.NONE;
+    }
+
+    @Override
+    public NodeId getMaster(NetworkId networkId, DeviceId deviceId) {
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        return leadershipService.getLeader(createDeviceMastershipTopic(networkId, deviceId));
+    }
+
+    @Override
+    public RoleInfo getNodes(NetworkId networkId, DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+        Leadership leadership = leadershipService.getLeadership(createDeviceMastershipTopic(networkId, deviceId));
+        return new RoleInfo(leadership.leaderNodeId(), leadership.candidates());
+    }
+
+    @Override
+    public MastershipInfo getMastership(NetworkId networkId, DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+        Leadership leadership = leadershipService.getLeadership(createDeviceMastershipTopic(networkId, deviceId));
+        return buildMastershipFromLeadership(leadership);
+    }
+
+    @Override
+    public Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(nodeId != null, NODE_ID_NULL);
+
+        // FIXME This result contains REMOVED device.
+        // MastershipService cannot listen to DeviceEvent to GC removed topic,
+        // since DeviceManager depend on it.
+        // Reference count, etc. at LeadershipService layer?
+        return leadershipService
+                .ownedTopics(nodeId)
+                .stream()
+                .filter(this::isVirtualMastershipTopic)
+                .map(this::extractDeviceIdFromTopic)
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
+                                                        NodeId nodeId, DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(nodeId != null, NODE_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+        if (leadershipAdminService.promoteToTopOfCandidateList(leadershipTopic, nodeId)) {
+            transferExecutor.schedule(() -> leadershipAdminService.transferLeadership(leadershipTopic, nodeId),
+                                      WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS, TimeUnit.MILLISECONDS);
+        }
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+        Leadership leadership = leadershipService.getLeadership(leadershipTopic);
+        return leadership != null && leadership.leaderNodeId() != null ?
+                MastershipTerm.of(leadership.leaderNodeId(),
+                                  leadership.leader().term()) : null;
+    }
+
+    @Override
+    public CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
+                                                         NodeId nodeId,
+                                                         DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(nodeId != null, NODE_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        NodeId currentMaster = getMaster(networkId, deviceId);
+        if (!nodeId.equals(currentMaster)) {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+        List<NodeId> candidates = leadershipService.getCandidates(leadershipTopic);
+
+        NodeId newMaster = candidates.stream()
+                .filter(candidate -> !Objects.equal(nodeId, candidate))
+                .findFirst()
+                .orElse(null);
+        log.info("Transitioning to role {} for {}. Next master: {}",
+                 newMaster != null ? MastershipRole.STANDBY : MastershipRole.NONE,
+                 deviceId, newMaster);
+
+        if (newMaster != null) {
+            return setMaster(networkId, newMaster, deviceId);
+        }
+        return relinquishRole(networkId, nodeId, deviceId);
+    }
+
+    @Override
+    public CompletableFuture<MastershipEvent> relinquishRole(NetworkId networkId,
+                                                             NodeId nodeId,
+                                                             DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(nodeId != null, NODE_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        if (nodeId.equals(localNodeId)) {
+            return relinquishLocalRoleByNetwork(networkId, deviceId);
+        }
+
+        log.debug("Forwarding request to relinquish "
+                          + "role for vnet {} device {} to {}", deviceId, nodeId);
+        return clusterCommunicator.sendAndReceive(
+                new VirtualDeviceId(networkId, deviceId),
+                ROLE_RELINQUISH_SUBJECT,
+                SERIALIZER::encode,
+                SERIALIZER::decode,
+                nodeId);
+    }
+
+    private CompletableFuture<MastershipEvent> relinquishLocalRoleByNetwork(NetworkId networkId,
+                                                                   DeviceId deviceId) {
+        checkArgument(networkId != null, NETWORK_ID_NULL);
+        checkArgument(deviceId != null, DEVICE_ID_NULL);
+
+        String leadershipTopic = createDeviceMastershipTopic(networkId, deviceId);
+        if (!leadershipService.getCandidates(leadershipTopic).contains(localNodeId)) {
+            return CompletableFuture.completedFuture(null);
+        }
+        MastershipEvent.Type eventType = localNodeId.equals(leadershipService.getLeader(leadershipTopic)) ?
+                MastershipEvent.Type.MASTER_CHANGED : MastershipEvent.Type.BACKUPS_CHANGED;
+        leadershipService.withdraw(leadershipTopic);
+        return CompletableFuture.completedFuture(
+            new MastershipEvent(eventType, deviceId, getMastership(networkId, deviceId)));
+    }
+
+    private CompletableFuture<MastershipEvent>
+    relinquishLocalRole(VirtualDeviceId virtualDeviceId) {
+        return relinquishLocalRoleByNetwork(virtualDeviceId.networkId,
+                                            virtualDeviceId.deviceId);
+    }
+
+    @Override
+    public void relinquishAllRole(NetworkId networkId, NodeId nodeId) {
+        // Noop. LeadershipService already takes care of detecting and purging stale locks.
+    }
+
+    private MastershipInfo buildMastershipFromLeadership(Leadership leadership) {
+        ImmutableMap.Builder<NodeId, MastershipRole> builder = ImmutableMap.builder();
+        if (leadership.leaderNodeId() != null) {
+            builder.put(leadership.leaderNodeId(), MastershipRole.MASTER);
+        }
+        leadership.candidates().forEach(nodeId -> builder.put(nodeId, MastershipRole.STANDBY));
+        clusterService.getNodes().stream()
+            .filter(node -> !leadership.candidates().contains(node.id()))
+            .forEach(node -> builder.put(node.id(), MastershipRole.NONE));
+
+        return new MastershipInfo(
+            leadership.leader() != null ? leadership.leader().term() : 0,
+            leadership.leader() != null
+                ? Optional.of(leadership.leader().nodeId())
+                : Optional.empty(),
+            builder.build());
+    }
+
+    private class InternalDeviceMastershipEventListener
+            implements LeadershipEventListener {
+
+        @Override
+        public boolean isRelevant(LeadershipEvent event) {
+            Leadership leadership = event.subject();
+            return isVirtualMastershipTopic(leadership.topic());
+        }
+
+        @Override
+        public void event(LeadershipEvent event) {
+            eventHandler.execute(() -> handleEvent(event));
+        }
+
+        private void handleEvent(LeadershipEvent event) {
+            Leadership leadership = event.subject();
+
+            NetworkId networkId = extractNetworkIdFromTopic(leadership.topic());
+            DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic());
+            MastershipInfo mastershipInfo = event.type() != LeadershipEvent.Type.SERVICE_DISRUPTED
+                ? buildMastershipFromLeadership(event.subject())
+                : new MastershipInfo();
+
+            switch (event.type()) {
+                case LEADER_AND_CANDIDATES_CHANGED:
+                    notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId, mastershipInfo));
+                    notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId, mastershipInfo));
+                    break;
+                case LEADER_CHANGED:
+                    notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId, mastershipInfo));
+                    break;
+                case CANDIDATES_CHANGED:
+                    notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId, mastershipInfo));
+                    break;
+                case SERVICE_DISRUPTED:
+                    notifyDelegate(networkId, new MastershipEvent(SUSPENDED, deviceId, mastershipInfo));
+                    break;
+                case SERVICE_RESTORED:
+                    // Do nothing, wait for updates from peers
+                    break;
+                default:
+            }
+        }
+    }
+
+    private String createDeviceMastershipTopic(NetworkId networkId, DeviceId deviceId) {
+        return String.format("vnet:%s,device:%s", networkId.toString(), deviceId.toString());
+    }
+
+    /**
+     * Returns the virtual network identifier extracted from the topic.
+     *
+     * @param topic topic to extract virtual network identifier
+     * @return an extracted virtual network identifier
+     * @throws IllegalArgumentException the topic not match with the pattern
+     * used for virtual network mastership store
+     */
+    private NetworkId extractNetworkIdFromTopic(String topic) {
+        Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+        if (m.matches()) {
+            return NetworkId.networkId(Long.getLong(m.group(1)));
+        } else {
+            throw new IllegalArgumentException("Invalid virtual mastership topic: "
+                                                       + topic);
+        }
+    }
+
+    /**
+     * Returns the device identifier extracted from the topic.
+     *
+     * @param topic topic to extract device identifier
+     * @return an extracted virtual device identifier
+     * @throws IllegalArgumentException the topic not match with the pattern
+     * used for virtual network mastership store
+     */
+    private DeviceId extractDeviceIdFromTopic(String topic) {
+        Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+        if (m.matches()) {
+            return DeviceId.deviceId(m.group(2));
+        } else {
+            throw new IllegalArgumentException("Invalid virtual mastership topic: "
+                                                       + topic);
+        }
+    }
+
+    /**
+     * Returns whether the topic is matched with virtual mastership store topic.
+     *
+     * @param topic topic to match
+     * @return True when the topic matched with virtual network mastership store
+     */
+    private boolean isVirtualMastershipTopic(String topic) {
+        Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
+        return m.matches();
+    }
+
+    /**
+     * A wrapper class used for the communication service.
+     */
+    private class VirtualDeviceId {
+        NetworkId networkId;
+        DeviceId deviceId;
+
+        public VirtualDeviceId(NetworkId networkId, DeviceId deviceId) {
+            this.networkId = networkId;
+            this.deviceId = deviceId;
+        }
+
+        public int hashCode() {
+            return Objects.hashCode(networkId, deviceId);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof VirtualDeviceId) {
+                final VirtualDeviceId that = (VirtualDeviceId) obj;
+                return this.getClass() == that.getClass() &&
+                        Objects.equal(this.networkId, that.networkId) &&
+                        Objects.equal(this.deviceId, that.deviceId);
+            }
+            return false;
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowObjectiveStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowObjectiveStore.java
new file mode 100644
index 0000000..5bcfabc
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowObjectiveStore.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Maps;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ConcurrentMap;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed flow objective store for virtual network.
+ */
+@Component(immediate = true, enabled = false, service = VirtualNetworkFlowObjectiveStore.class)
+public class DistributedVirtualFlowObjectiveStore
+        extends SimpleVirtualFlowObjectiveStore
+        implements VirtualNetworkFlowObjectiveStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private ConsistentMap<NetworkId, ConcurrentMap<Integer, byte[]>> nextGroupsMap;
+    private static final String VNET_FLOW_OBJ_GROUP_MAP_NAME =
+            "onos-networkId-flowobjective-groups";
+    private static final String VNET_FLOW_OBJ_GROUP_MAP_FRIENDLYNAME =
+            "DistributedVirtualFlowObjectiveStore";
+
+    @Override
+    protected void initNextGroupsMap() {
+        nextGroupsMap = storageService.<NetworkId, ConcurrentMap<Integer, byte[]>>consistentMapBuilder()
+                .withName(VNET_FLOW_OBJ_GROUP_MAP_NAME)
+                .withSerializer(Serializer.using(
+                        new KryoNamespace.Builder()
+                                .register(KryoNamespaces.API)
+                                .register(NetworkId.class)
+                                .build(VNET_FLOW_OBJ_GROUP_MAP_FRIENDLYNAME)))
+                .build();
+
+    }
+
+    @Override
+    protected ConcurrentMap<Integer, byte[]> getNextGroups(NetworkId networkId) {
+        nextGroupsMap.computeIfAbsent(networkId, n -> {
+            log.debug("getNextGroups - creating new ConcurrentMap");
+            return Maps.newConcurrentMap();
+        });
+
+        return nextGroupsMap.get(networkId).value();
+    }
+
+    @Override
+    protected void updateNextGroupsMap(NetworkId networkId, ConcurrentMap<Integer,
+            byte[]> nextGroups) {
+        nextGroupsMap.put(networkId, nextGroups);
+    }
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowRuleStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowRuleStore.java
new file mode 100644
index 0000000..8722c92
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualFlowRuleStore.java
@@ -0,0 +1,922 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Futures;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualDeviceId;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowEntry;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowRule;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowRuleBatchEvent;
+import org.onosproject.incubator.net.virtual.store.impl.primitives.VirtualFlowRuleBatchOperation;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.BatchOperationEntry;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
+import org.onosproject.store.Timestamp;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.*;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of flow rules using a distributed state management protocol
+ * for virtual networks.
+ */
+//TODO: support backup and persistent mechanism
+@Component(immediate = true, enabled = false, service = VirtualNetworkFlowRuleStore.class,
+        property = {
+                MESSAGE_HANDLER_THREAD_POOL_SIZE + ":Integer=" + MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT,
+                BACKUP_PERIOD_MILLIS + ":Integer=" + BACKUP_PERIOD_MILLIS_DEFAULT,
+                PERSISTENCE_ENABLED + ":Boolean=" + PERSISTENCE_ENABLED_DEFAULT,
+        })
+
+public class DistributedVirtualFlowRuleStore
+        extends AbstractVirtualStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+        implements VirtualNetworkFlowRuleStore {
+
+    private final Logger log = getLogger(getClass());
+
+    //TODO: confirm this working fine with multiple thread more than 1
+    private static final long FLOW_RULE_STORE_TIMEOUT_MILLIS = 5000;
+
+    private static final String FLOW_OP_TOPIC = "virtual-flow-ops-ids";
+
+    // MessageSubjects used by DistributedVirtualFlowRuleStore peer-peer communication.
+    private static final MessageSubject APPLY_BATCH_FLOWS
+            = new MessageSubject("virtual-peer-forward-apply-batch");
+    private static final MessageSubject GET_FLOW_ENTRY
+            = new MessageSubject("virtual-peer-forward-get-flow-entry");
+    private static final MessageSubject GET_DEVICE_FLOW_ENTRIES
+            = new MessageSubject("virtual-peer-forward-get-device-flow-entries");
+    private static final MessageSubject REMOVE_FLOW_ENTRY
+            = new MessageSubject("virtual-peer-forward-remove-flow-entry");
+    private static final MessageSubject REMOTE_APPLY_COMPLETED
+            = new MessageSubject("virtual-peer-apply-completed");
+
+    /** Number of threads in the message handler pool. */
+    private int msgHandlerThreadPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+
+    /** Delay in ms between successive backup runs. */
+    private int backupPeriod = BACKUP_PERIOD_MILLIS_DEFAULT;
+
+    /** Indicates whether or not changes in the flow table should be persisted to disk.. */
+    private boolean persistenceEnabled = PERSISTENCE_ENABLED_DEFAULT;
+
+    private InternalFlowTable flowTable = new InternalFlowTable();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterCommunicationService clusterCommunicator;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VirtualNetworkService vnaService;
+
+    private Map<Long, NodeId> pendingResponses = Maps.newConcurrentMap();
+    private ExecutorService messageHandlingExecutor;
+    private ExecutorService eventHandler;
+
+    private EventuallyConsistentMap<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>> deviceTableStats;
+    private final EventuallyConsistentMapListener<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>>
+            tableStatsListener = new InternalTableStatsListener();
+
+
+    protected final Serializer serializer = Serializer.using(KryoNamespace.newBuilder()
+                                                                     .register(KryoNamespaces.API)
+                                                                     .register(NetworkId.class)
+                                                                     .register(VirtualFlowRuleBatchOperation.class)
+                                                                     .register(VirtualFlowRuleBatchEvent.class)
+                                                                     .build());
+
+    protected final KryoNamespace.Builder serializerBuilder = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API)
+            .register(MastershipBasedTimestamp.class);
+
+    private IdGenerator idGenerator;
+    private NodeId local;
+
+
+    @Activate
+    public void activate(ComponentContext context) {
+        configService.registerProperties(getClass());
+
+        idGenerator = coreService.getIdGenerator(FLOW_OP_TOPIC);
+
+        local = clusterService.getLocalNode().id();
+
+        eventHandler = Executors.newSingleThreadExecutor(
+                groupedThreads("onos/virtual-flow", "event-handler", log));
+        messageHandlingExecutor = Executors.newFixedThreadPool(
+                msgHandlerThreadPoolSize, groupedThreads("onos/store/virtual-flow", "message-handlers", log));
+
+        registerMessageHandlers(messageHandlingExecutor);
+
+        deviceTableStats = storageService
+                .<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>>eventuallyConsistentMapBuilder()
+                .withName("onos-virtual-flow-table-stats")
+                .withSerializer(serializerBuilder)
+                .withAntiEntropyPeriod(5, TimeUnit.SECONDS)
+                .withTimestampProvider((k, v) -> new WallClockTimestamp())
+                .withTombstonesDisabled()
+                .build();
+        deviceTableStats.addListener(tableStatsListener);
+
+        logConfig("Started");
+    }
+
+    @Deactivate
+    public void deactivate(ComponentContext context) {
+        configService.unregisterProperties(getClass(), false);
+        unregisterMessageHandlers();
+        deviceTableStats.removeListener(tableStatsListener);
+        deviceTableStats.destroy();
+        eventHandler.shutdownNow();
+        messageHandlingExecutor.shutdownNow();
+        log.info("Stopped");
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Modified
+    public void modified(ComponentContext context) {
+        if (context == null) {
+            logConfig("Default config");
+            return;
+        }
+
+        Dictionary properties = context.getProperties();
+        int newPoolSize;
+        int newBackupPeriod;
+        try {
+            String s = get(properties, MESSAGE_HANDLER_THREAD_POOL_SIZE);
+            newPoolSize = isNullOrEmpty(s) ? msgHandlerThreadPoolSize : Integer.parseInt(s.trim());
+
+            s = get(properties, BACKUP_PERIOD_MILLIS);
+            newBackupPeriod = isNullOrEmpty(s) ? backupPeriod : Integer.parseInt(s.trim());
+        } catch (NumberFormatException | ClassCastException e) {
+            newPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+            newBackupPeriod = BACKUP_PERIOD_MILLIS_DEFAULT;
+        }
+
+        boolean restartBackupTask = false;
+
+        if (newBackupPeriod != backupPeriod) {
+            backupPeriod = newBackupPeriod;
+            restartBackupTask = true;
+        }
+        if (restartBackupTask) {
+            log.warn("Currently, backup tasks are not supported.");
+        }
+        if (newPoolSize != msgHandlerThreadPoolSize) {
+            msgHandlerThreadPoolSize = newPoolSize;
+            ExecutorService oldMsgHandler = messageHandlingExecutor;
+            messageHandlingExecutor = Executors.newFixedThreadPool(
+                    msgHandlerThreadPoolSize, groupedThreads("onos/store/virtual-flow", "message-handlers", log));
+
+            // replace previously registered handlers.
+            registerMessageHandlers(messageHandlingExecutor);
+            oldMsgHandler.shutdown();
+        }
+
+        logConfig("Reconfigured");
+    }
+
+    @Override
+    public int getFlowRuleCount(NetworkId networkId) {
+        AtomicInteger sum = new AtomicInteger(0);
+        DeviceService deviceService = vnaService.get(networkId, DeviceService.class);
+        deviceService.getDevices()
+                .forEach(device -> sum.addAndGet(
+                        Iterables.size(getFlowEntries(networkId, device.id()))));
+        return sum.get();
+    }
+
+    @Override
+    public FlowEntry getFlowEntry(NetworkId networkId, FlowRule rule) {
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        NodeId master = mastershipService.getMasterFor(rule.deviceId());
+
+        if (master == null) {
+            log.debug("Failed to getFlowEntry: No master for {}, vnet {}",
+                      rule.deviceId(), networkId);
+            return null;
+        }
+
+        if (Objects.equals(local, master)) {
+            return flowTable.getFlowEntry(networkId, rule);
+        }
+
+        log.trace("Forwarding getFlowEntry to {}, which is the primary (master) " +
+                          "for device {}, vnet {}",
+                  master, rule.deviceId(), networkId);
+
+        VirtualFlowRule vRule = new VirtualFlowRule(networkId, rule);
+
+        return Tools.futureGetOrElse(clusterCommunicator.sendAndReceive(vRule,
+                                                                        GET_FLOW_ENTRY,
+                                                                        serializer::encode,
+                                                                        serializer::decode,
+                                                                        master),
+                                     FLOW_RULE_STORE_TIMEOUT_MILLIS,
+                                     TimeUnit.MILLISECONDS,
+                                     null);
+    }
+
+    @Override
+    public Iterable<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        NodeId master = mastershipService.getMasterFor(deviceId);
+
+        if (master == null) {
+            log.debug("Failed to getFlowEntries: No master for {}, vnet {}", deviceId, networkId);
+            return Collections.emptyList();
+        }
+
+        if (Objects.equals(local, master)) {
+            return flowTable.getFlowEntries(networkId, deviceId);
+        }
+
+        log.trace("Forwarding getFlowEntries to {}, which is the primary (master) for device {}",
+                  master, deviceId);
+
+        return Tools.futureGetOrElse(
+                clusterCommunicator.sendAndReceive(deviceId,
+                                                   GET_DEVICE_FLOW_ENTRIES,
+                                                   serializer::encode,
+                                                   serializer::decode,
+                                                   master),
+                FLOW_RULE_STORE_TIMEOUT_MILLIS,
+                TimeUnit.MILLISECONDS,
+                Collections.emptyList());
+    }
+
+    @Override
+    public void storeBatch(NetworkId networkId, FlowRuleBatchOperation operation) {
+        if (operation.getOperations().isEmpty()) {
+            notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+                    new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+                    new CompletedBatchOperation(true, Collections.emptySet(), operation.deviceId())));
+            return;
+        }
+
+        DeviceId deviceId = operation.deviceId();
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        NodeId master = mastershipService.getMasterFor(deviceId);
+
+        if (master == null) {
+            log.warn("No master for {}, vnet {} : flows will be marked for removal", deviceId, networkId);
+
+            updateStoreInternal(networkId, operation);
+
+            notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+                    new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+                    new CompletedBatchOperation(true, Collections.emptySet(), operation.deviceId())));
+            return;
+        }
+
+        if (Objects.equals(local, master)) {
+            storeBatchInternal(networkId, operation);
+            return;
+        }
+
+        log.trace("Forwarding storeBatch to {}, which is the primary (master) for device {}, vent {}",
+                  master, deviceId, networkId);
+
+        clusterCommunicator.unicast(new VirtualFlowRuleBatchOperation(networkId, operation),
+                                    APPLY_BATCH_FLOWS,
+                                    serializer::encode,
+                                    master)
+                .whenComplete((result, error) -> {
+                    if (error != null) {
+                        log.warn("Failed to storeBatch: {} to {}", operation, master, error);
+
+                        Set<FlowRule> allFailures = operation.getOperations()
+                                .stream()
+                                .map(BatchOperationEntry::target)
+                                .collect(Collectors.toSet());
+
+                        notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+                                new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+                                new CompletedBatchOperation(false, allFailures, deviceId)));
+                    }
+                });
+    }
+
+    @Override
+    public void batchOperationComplete(NetworkId networkId, FlowRuleBatchEvent event) {
+        //FIXME: need a per device pending response
+        NodeId nodeId = pendingResponses.remove(event.subject().batchId());
+        if (nodeId == null) {
+            notifyDelegate(networkId, event);
+        } else {
+            // TODO check unicast return value
+            clusterCommunicator.unicast(new VirtualFlowRuleBatchEvent(networkId, event),
+                                        REMOTE_APPLY_COMPLETED, serializer::encode, nodeId);
+            //error log: log.warn("Failed to respond to peer for batch operation result");
+        }
+    }
+
+    @Override
+    public void deleteFlowRule(NetworkId networkId, FlowRule rule) {
+        storeBatch(networkId,
+                new FlowRuleBatchOperation(
+                        Collections.singletonList(
+                                new FlowRuleBatchEntry(
+                                        FlowRuleBatchEntry.FlowRuleOperation.REMOVE,
+                                        rule)), rule.deviceId(), idGenerator.getNewId()));
+    }
+
+    @Override
+    public FlowRuleEvent addOrUpdateFlowRule(NetworkId networkId, FlowEntry rule) {
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        NodeId master = mastershipService.getMasterFor(rule.deviceId());
+        if (Objects.equals(local, master)) {
+            return addOrUpdateFlowRuleInternal(networkId, rule);
+        }
+
+        log.warn("Tried to update FlowRule {} state,"
+                         + " while the Node was not the master.", rule);
+        return null;
+    }
+
+    private FlowRuleEvent addOrUpdateFlowRuleInternal(NetworkId networkId, FlowEntry rule) {
+        // check if this new rule is an update to an existing entry
+        StoredFlowEntry stored = flowTable.getFlowEntry(networkId, rule);
+        if (stored != null) {
+            //FIXME modification of "stored" flow entry outside of flow table
+            stored.setBytes(rule.bytes());
+            stored.setLife(rule.life(TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS);
+            stored.setLiveType(rule.liveType());
+            stored.setPackets(rule.packets());
+            stored.setLastSeen();
+            if (stored.state() == FlowEntry.FlowEntryState.PENDING_ADD) {
+                stored.setState(FlowEntry.FlowEntryState.ADDED);
+                return new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, rule);
+            }
+            return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+        }
+
+        // TODO: Confirm if this behavior is correct. See SimpleFlowRuleStore
+        // TODO: also update backup if the behavior is correct.
+        flowTable.add(networkId, rule);
+        return null;
+    }
+
+    @Override
+    public FlowRuleEvent removeFlowRule(NetworkId networkId, FlowEntry rule) {
+        final DeviceId deviceId = rule.deviceId();
+
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        NodeId master = mastershipService.getMasterFor(deviceId);
+
+        if (Objects.equals(local, master)) {
+            // bypass and handle it locally
+            return removeFlowRuleInternal(new VirtualFlowEntry(networkId, rule));
+        }
+
+        if (master == null) {
+            log.warn("Failed to removeFlowRule: No master for {}", deviceId);
+            // TODO: revisit if this should be null (="no-op") or Exception
+            return null;
+        }
+
+        log.trace("Forwarding removeFlowRule to {}, which is the master for device {}",
+                  master, deviceId);
+
+        return Futures.getUnchecked(clusterCommunicator.sendAndReceive(
+                new VirtualFlowEntry(networkId, rule),
+                REMOVE_FLOW_ENTRY,
+                serializer::encode,
+                serializer::decode,
+                master));
+    }
+
+    @Override
+    public FlowRuleEvent pendingFlowRule(NetworkId networkId, FlowEntry rule) {
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        if (mastershipService.isLocalMaster(rule.deviceId())) {
+            StoredFlowEntry stored = flowTable.getFlowEntry(networkId, rule);
+            if (stored != null &&
+                    stored.state() != FlowEntry.FlowEntryState.PENDING_ADD) {
+                stored.setState(FlowEntry.FlowEntryState.PENDING_ADD);
+                return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void purgeFlowRules(NetworkId networkId) {
+        flowTable.purgeFlowRules(networkId);
+    }
+
+    @Override
+    public FlowRuleEvent updateTableStatistics(NetworkId networkId,
+                                               DeviceId deviceId,
+                                               List<TableStatisticsEntry> tableStats) {
+        if (deviceTableStats.get(networkId) == null) {
+            deviceTableStats.put(networkId, Maps.newConcurrentMap());
+        }
+        deviceTableStats.get(networkId).put(deviceId, tableStats);
+        return null;
+    }
+
+    @Override
+    public Iterable<TableStatisticsEntry> getTableStatistics(NetworkId networkId, DeviceId deviceId) {
+        MastershipService mastershipService =
+                vnaService.get(networkId, MastershipService.class);
+        NodeId master = mastershipService.getMasterFor(deviceId);
+
+        if (master == null) {
+            log.debug("Failed to getTableStats: No master for {}", deviceId);
+            return Collections.emptyList();
+        }
+
+        if (deviceTableStats.get(networkId) == null) {
+            deviceTableStats.put(networkId, Maps.newConcurrentMap());
+        }
+
+        List<TableStatisticsEntry> tableStats = deviceTableStats.get(networkId).get(deviceId);
+        if (tableStats == null) {
+            return Collections.emptyList();
+        }
+        return ImmutableList.copyOf(tableStats);
+    }
+
+    private void registerMessageHandlers(ExecutorService executor) {
+        clusterCommunicator.addSubscriber(APPLY_BATCH_FLOWS, new OnStoreBatch(), executor);
+        clusterCommunicator.<VirtualFlowRuleBatchEvent>addSubscriber(
+                REMOTE_APPLY_COMPLETED, serializer::decode,
+                this::notifyDelicateByNetwork, executor);
+        clusterCommunicator.addSubscriber(
+                GET_FLOW_ENTRY, serializer::decode, this::getFlowEntryByNetwork,
+                serializer::encode, executor);
+        clusterCommunicator.addSubscriber(
+                GET_DEVICE_FLOW_ENTRIES, serializer::decode,
+                this::getFlowEntriesByNetwork,
+                serializer::encode, executor);
+        clusterCommunicator.addSubscriber(
+                REMOVE_FLOW_ENTRY, serializer::decode, this::removeFlowRuleInternal,
+                serializer::encode, executor);
+    }
+
+    private void unregisterMessageHandlers() {
+        clusterCommunicator.removeSubscriber(REMOVE_FLOW_ENTRY);
+        clusterCommunicator.removeSubscriber(GET_DEVICE_FLOW_ENTRIES);
+        clusterCommunicator.removeSubscriber(GET_FLOW_ENTRY);
+        clusterCommunicator.removeSubscriber(APPLY_BATCH_FLOWS);
+        clusterCommunicator.removeSubscriber(REMOTE_APPLY_COMPLETED);
+    }
+
+
+    private void logConfig(String prefix) {
+        log.info("{} with msgHandlerPoolSize = {}; backupPeriod = {}",
+                 prefix, msgHandlerThreadPoolSize, backupPeriod);
+    }
+
+    private void storeBatchInternal(NetworkId networkId, FlowRuleBatchOperation operation) {
+
+        final DeviceId did = operation.deviceId();
+        //final Collection<FlowEntry> ft = flowTable.getFlowEntries(did);
+        Set<FlowRuleBatchEntry> currentOps = updateStoreInternal(networkId, operation);
+        if (currentOps.isEmpty()) {
+            batchOperationComplete(networkId, FlowRuleBatchEvent.completed(
+                    new FlowRuleBatchRequest(operation.id(), Collections.emptySet()),
+                    new CompletedBatchOperation(true, Collections.emptySet(), did)));
+            return;
+        }
+
+        //Confirm that flowrule service is created
+        vnaService.get(networkId, FlowRuleService.class);
+
+        notifyDelegate(networkId, FlowRuleBatchEvent.requested(new
+                                                            FlowRuleBatchRequest(operation.id(),
+                                                                                 currentOps), operation.deviceId()));
+    }
+
+    private Set<FlowRuleBatchEntry> updateStoreInternal(NetworkId networkId,
+                                                        FlowRuleBatchOperation operation) {
+        return operation.getOperations().stream().map(
+                op -> {
+                    StoredFlowEntry entry;
+                    switch (op.operator()) {
+                        case ADD:
+                            entry = new DefaultFlowEntry(op.target());
+                            // always add requested FlowRule
+                            // Note: 2 equal FlowEntry may have different treatment
+                            flowTable.remove(networkId, entry.deviceId(), entry);
+                            flowTable.add(networkId, entry);
+
+                            return op;
+                        case REMOVE:
+                            entry = flowTable.getFlowEntry(networkId, op.target());
+                            if (entry != null) {
+                                //FIXME modification of "stored" flow entry outside of flow table
+                                entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
+                                log.debug("Setting state of rule to pending remove: {}", entry);
+                                return op;
+                            }
+                            break;
+                        case MODIFY:
+                            //TODO: figure this out at some point
+                            break;
+                        default:
+                            log.warn("Unknown flow operation operator: {}", op.operator());
+                    }
+                    return null;
+                }
+        ).filter(Objects::nonNull).collect(Collectors.toSet());
+    }
+
+    private FlowRuleEvent removeFlowRuleInternal(VirtualFlowEntry rule) {
+        final DeviceId deviceId = rule.flowEntry().deviceId();
+        // This is where one could mark a rule as removed and still keep it in the store.
+        final FlowEntry removed = flowTable.remove(rule.networkId(), deviceId, rule.flowEntry());
+        // rule may be partial rule that is missing treatment, we should use rule from store instead
+        return removed != null ? new FlowRuleEvent(RULE_REMOVED, removed) : null;
+    }
+
+    private final class OnStoreBatch implements ClusterMessageHandler {
+
+        @Override
+        public void handle(final ClusterMessage message) {
+            VirtualFlowRuleBatchOperation vOperation = serializer.decode(message.payload());
+            log.debug("received batch request {}", vOperation);
+
+            FlowRuleBatchOperation operation = vOperation.operation();
+
+            final DeviceId deviceId = operation.deviceId();
+            MastershipService mastershipService =
+                    vnaService.get(vOperation.networkId(), MastershipService.class);
+            NodeId master = mastershipService.getMasterFor(deviceId);
+            if (!Objects.equals(local, master)) {
+                Set<FlowRule> failures = new HashSet<>(operation.size());
+                for (FlowRuleBatchEntry op : operation.getOperations()) {
+                    failures.add(op.target());
+                }
+                CompletedBatchOperation allFailed = new CompletedBatchOperation(false, failures, deviceId);
+                // This node is no longer the master, respond as all failed.
+                // TODO: we might want to wrap response in envelope
+                // to distinguish sw programming failure and hand over
+                // it make sense in the latter case to retry immediately.
+                message.respond(serializer.encode(allFailed));
+                return;
+            }
+
+            pendingResponses.put(operation.id(), message.sender());
+            storeBatchInternal(vOperation.networkId(), operation);
+        }
+    }
+
+    /**
+     * Returns flow rule entry using virtual flow rule.
+     *
+     * @param rule an encapsulated flow rule to be queried
+     */
+    private FlowEntry getFlowEntryByNetwork(VirtualFlowRule rule) {
+        return flowTable.getFlowEntry(rule.networkId(), rule.rule());
+    }
+
+    /**
+     * returns flow entries using virtual device id.
+     *
+     * @param deviceId an encapsulated virtual device id
+     * @return a set of flow entries
+     */
+    private Set<FlowEntry> getFlowEntriesByNetwork(VirtualDeviceId deviceId) {
+        return flowTable.getFlowEntries(deviceId.networkId(), deviceId.deviceId());
+    }
+
+    /**
+     * span out Flow Rule Batch event according to virtual network id.
+     *
+     * @param event a event to be span out
+     */
+    private void notifyDelicateByNetwork(VirtualFlowRuleBatchEvent event) {
+        batchOperationComplete(event.networkId(), event.event());
+    }
+
+    private class InternalFlowTable {
+        //TODO replace the Map<V,V> with ExtendedSet
+        //TODO: support backup mechanism
+        private final Map<NetworkId, Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>>
+                flowEntriesMap = Maps.newConcurrentMap();
+        private final Map<NetworkId, Map<DeviceId, Long>> lastUpdateTimesMap = Maps.newConcurrentMap();
+
+        private Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+        getFlowEntriesByNetwork(NetworkId networkId) {
+            return flowEntriesMap.computeIfAbsent(networkId, k -> Maps.newConcurrentMap());
+        }
+
+        private Map<DeviceId, Long> getLastUpdateTimesByNetwork(NetworkId networkId) {
+            return lastUpdateTimesMap.computeIfAbsent(networkId, k -> Maps.newConcurrentMap());
+        }
+
+        /**
+         * Returns the flow table for specified device.
+         *
+         * @param networkId virtual network identifier
+         * @param deviceId identifier of the device
+         * @return Map representing Flow Table of given device.
+         */
+        private Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>
+        getFlowTable(NetworkId networkId, DeviceId deviceId) {
+            Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+                    flowEntries = getFlowEntriesByNetwork(networkId);
+            if (persistenceEnabled) {
+                //TODO: support persistent
+                log.warn("Persistent is not supported");
+                return null;
+            } else {
+                return flowEntries.computeIfAbsent(deviceId, id -> Maps.newConcurrentMap());
+            }
+        }
+
+        private Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>
+        getFlowTableCopy(NetworkId networkId, DeviceId deviceId) {
+
+            Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+                    flowEntries = getFlowEntriesByNetwork(networkId);
+            Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>> copy = Maps.newHashMap();
+
+            if (persistenceEnabled) {
+                //TODO: support persistent
+                log.warn("Persistent is not supported");
+                return null;
+            } else {
+                flowEntries.computeIfAbsent(deviceId, id -> Maps.newConcurrentMap()).forEach((k, v) -> {
+                    copy.put(k, Maps.newHashMap(v));
+                });
+                return copy;
+            }
+        }
+
+        private Map<StoredFlowEntry, StoredFlowEntry>
+        getFlowEntriesInternal(NetworkId networkId, DeviceId deviceId, FlowId flowId) {
+
+            return getFlowTable(networkId, deviceId)
+                    .computeIfAbsent(flowId, id -> Maps.newConcurrentMap());
+        }
+
+        private StoredFlowEntry getFlowEntryInternal(NetworkId networkId, FlowRule rule) {
+
+            return getFlowEntriesInternal(networkId, rule.deviceId(), rule.id()).get(rule);
+        }
+
+        private Set<FlowEntry> getFlowEntriesInternal(NetworkId networkId, DeviceId deviceId) {
+
+            return getFlowTable(networkId, deviceId).values().stream()
+                    .flatMap(m -> m.values().stream())
+                    .collect(Collectors.toSet());
+        }
+
+        public StoredFlowEntry getFlowEntry(NetworkId networkId, FlowRule rule) {
+            return getFlowEntryInternal(networkId, rule);
+        }
+
+        public Set<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
+
+            return getFlowEntriesInternal(networkId, deviceId);
+        }
+
+        public void add(NetworkId networkId, FlowEntry rule) {
+            Map<DeviceId, Long> lastUpdateTimes = getLastUpdateTimesByNetwork(networkId);
+
+            getFlowEntriesInternal(networkId, rule.deviceId(), rule.id())
+                    .compute((StoredFlowEntry) rule, (k, stored) -> {
+                        //TODO compare stored and rule timestamps
+                        //TODO the key is not updated
+                        return (StoredFlowEntry) rule;
+                    });
+            lastUpdateTimes.put(rule.deviceId(), System.currentTimeMillis());
+        }
+
+        public FlowEntry remove(NetworkId networkId, DeviceId deviceId, FlowEntry rule) {
+            final AtomicReference<FlowEntry> removedRule = new AtomicReference<>();
+            Map<DeviceId, Long> lastUpdateTimes = getLastUpdateTimesByNetwork(networkId);
+
+            getFlowEntriesInternal(networkId, rule.deviceId(), rule.id())
+                    .computeIfPresent((StoredFlowEntry) rule, (k, stored) -> {
+                        if (rule instanceof DefaultFlowEntry) {
+                            DefaultFlowEntry toRemove = (DefaultFlowEntry) rule;
+                            if (stored instanceof DefaultFlowEntry) {
+                                DefaultFlowEntry storedEntry = (DefaultFlowEntry) stored;
+                                if (toRemove.created() < storedEntry.created()) {
+                                    log.debug("Trying to remove more recent flow entry {} (stored: {})",
+                                              toRemove, stored);
+                                    // the key is not updated, removedRule remains null
+                                    return stored;
+                                }
+                            }
+                        }
+                        removedRule.set(stored);
+                        return null;
+                    });
+
+            if (removedRule.get() != null) {
+                lastUpdateTimes.put(deviceId, System.currentTimeMillis());
+                return removedRule.get();
+            } else {
+                return null;
+            }
+        }
+
+        public void purgeFlowRule(NetworkId networkId, DeviceId deviceId) {
+            Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+                    flowEntries = getFlowEntriesByNetwork(networkId);
+            flowEntries.remove(deviceId);
+        }
+
+        public void purgeFlowRules(NetworkId networkId) {
+            Map<DeviceId, Map<FlowId, Map<StoredFlowEntry, StoredFlowEntry>>>
+                    flowEntries = getFlowEntriesByNetwork(networkId);
+            flowEntries.clear();
+        }
+    }
+
+    private class InternalTableStatsListener
+            implements EventuallyConsistentMapListener<NetworkId, Map<DeviceId, List<TableStatisticsEntry>>> {
+
+        @Override
+        public void event(EventuallyConsistentMapEvent<NetworkId, Map<DeviceId,
+                List<TableStatisticsEntry>>> event) {
+            //TODO: Generate an event to listeners (do we need?)
+        }
+    }
+
+    public final class MastershipBasedTimestamp implements Timestamp {
+
+        private final long termNumber;
+        private final long sequenceNumber;
+
+        /**
+         * Default constructor for serialization.
+         */
+        protected MastershipBasedTimestamp() {
+            this.termNumber = -1;
+            this.sequenceNumber = -1;
+        }
+
+        /**
+         * Default version tuple.
+         *
+         * @param termNumber the mastership termNumber
+         * @param sequenceNumber  the sequenceNumber number within the termNumber
+         */
+        public MastershipBasedTimestamp(long termNumber, long sequenceNumber) {
+            this.termNumber = termNumber;
+            this.sequenceNumber = sequenceNumber;
+        }
+
+        @Override
+        public int compareTo(Timestamp o) {
+            checkArgument(o instanceof MastershipBasedTimestamp,
+                          "Must be MastershipBasedTimestamp", o);
+            MastershipBasedTimestamp that = (MastershipBasedTimestamp) o;
+
+            return ComparisonChain.start()
+                    .compare(this.termNumber, that.termNumber)
+                    .compare(this.sequenceNumber, that.sequenceNumber)
+                    .result();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(termNumber, sequenceNumber);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof MastershipBasedTimestamp)) {
+                return false;
+            }
+            MastershipBasedTimestamp that = (MastershipBasedTimestamp) obj;
+            return Objects.equals(this.termNumber, that.termNumber) &&
+                    Objects.equals(this.sequenceNumber, that.sequenceNumber);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass())
+                    .add("termNumber", termNumber)
+                    .add("sequenceNumber", sequenceNumber)
+                    .toString();
+        }
+
+        /**
+         * Returns the termNumber.
+         *
+         * @return termNumber
+         */
+        public long termNumber() {
+            return termNumber;
+        }
+
+        /**
+         * Returns the sequenceNumber number.
+         *
+         * @return sequenceNumber
+         */
+        public long sequenceNumber() {
+            return sequenceNumber;
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualNetworkStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualNetworkStore.java
new file mode 100644
index 0000000..b756c62
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualNetworkStore.java
@@ -0,0 +1,954 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.DefaultVirtualDevice;
+import org.onosproject.incubator.net.virtual.DefaultVirtualHost;
+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.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkService;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStoreDelegate;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.Key;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.SetEvent;
+import org.onosproject.store.service.SetEventListener;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual network store.
+ */
+@Component(immediate = true, service = VirtualNetworkStore.class)
+public class DistributedVirtualNetworkStore
+        extends AbstractStore<VirtualNetworkEvent, VirtualNetworkStoreDelegate>
+        implements VirtualNetworkStore {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    private IdGenerator idGenerator;
+
+    // Track tenants by ID
+    private DistributedSet<TenantId> tenantIdSet;
+
+    // Listener for tenant events
+    private final SetEventListener<TenantId> setListener = new InternalSetListener();
+
+    // Track virtual networks by network Id
+    private ConsistentMap<NetworkId, VirtualNetwork> networkIdVirtualNetworkConsistentMap;
+    private Map<NetworkId, VirtualNetwork> networkIdVirtualNetworkMap;
+
+    // Listener for virtual network events
+    private final MapEventListener<NetworkId, VirtualNetwork> virtualNetworkMapListener =
+            new InternalMapListener<>((mapEventType, virtualNetwork) -> {
+                VirtualNetworkEvent.Type eventType =
+                    mapEventType.equals(MapEvent.Type.INSERT)
+                            ? VirtualNetworkEvent.Type.NETWORK_ADDED :
+                    mapEventType.equals(MapEvent.Type.UPDATE)
+                            ? VirtualNetworkEvent.Type.NETWORK_UPDATED :
+                    mapEventType.equals(MapEvent.Type.REMOVE)
+                            ? VirtualNetworkEvent.Type.NETWORK_REMOVED : null;
+                return eventType == null ? null : new VirtualNetworkEvent(eventType, virtualNetwork.id());
+            });
+
+    // Listener for virtual device events
+    private final MapEventListener<VirtualDeviceId, VirtualDevice> virtualDeviceMapListener =
+            new InternalMapListener<>((mapEventType, virtualDevice) -> {
+                VirtualNetworkEvent.Type eventType =
+                        mapEventType.equals(MapEvent.Type.INSERT)
+                                ? VirtualNetworkEvent.Type.VIRTUAL_DEVICE_ADDED :
+                        mapEventType.equals(MapEvent.Type.UPDATE)
+                                ? VirtualNetworkEvent.Type.VIRTUAL_DEVICE_UPDATED :
+                        mapEventType.equals(MapEvent.Type.REMOVE)
+                                ? VirtualNetworkEvent.Type.VIRTUAL_DEVICE_REMOVED : null;
+                return eventType == null ? null :
+                        new VirtualNetworkEvent(eventType, virtualDevice.networkId(), virtualDevice);
+            });
+
+    // Track virtual network IDs by tenant Id
+    private ConsistentMap<TenantId, Set<NetworkId>> tenantIdNetworkIdSetConsistentMap;
+    private Map<TenantId, Set<NetworkId>> tenantIdNetworkIdSetMap;
+
+    // Track virtual devices by device Id
+    private ConsistentMap<VirtualDeviceId, VirtualDevice> deviceIdVirtualDeviceConsistentMap;
+    private Map<VirtualDeviceId, VirtualDevice> deviceIdVirtualDeviceMap;
+
+    // Track device IDs by network Id
+    private ConsistentMap<NetworkId, Set<DeviceId>> networkIdDeviceIdSetConsistentMap;
+    private Map<NetworkId, Set<DeviceId>> networkIdDeviceIdSetMap;
+
+    // Track virtual hosts by host Id
+    private ConsistentMap<HostId, VirtualHost> hostIdVirtualHostConsistentMap;
+    private Map<HostId, VirtualHost> hostIdVirtualHostMap;
+
+    // Track host IDs by network Id
+    private ConsistentMap<NetworkId, Set<HostId>> networkIdHostIdSetConsistentMap;
+    private Map<NetworkId, Set<HostId>> networkIdHostIdSetMap;
+
+    // Track virtual links by network Id
+    private ConsistentMap<NetworkId, Set<VirtualLink>> networkIdVirtualLinkSetConsistentMap;
+    private Map<NetworkId, Set<VirtualLink>> networkIdVirtualLinkSetMap;
+
+    // Track virtual ports by network Id
+    private ConsistentMap<NetworkId, Set<VirtualPort>> networkIdVirtualPortSetConsistentMap;
+    private Map<NetworkId, Set<VirtualPort>> networkIdVirtualPortSetMap;
+
+    // Track intent ID to TunnelIds
+    private ConsistentMap<Key, Set<TunnelId>> intentKeyTunnelIdSetConsistentMap;
+    private Map<Key, Set<TunnelId>> intentKeyTunnelIdSetMap;
+
+    private static final Serializer SERIALIZER = Serializer
+            .using(new KryoNamespace.Builder().register(KryoNamespaces.API)
+                           .register(TenantId.class)
+                           .register(NetworkId.class)
+                           .register(VirtualNetwork.class)
+                           .register(DefaultVirtualNetwork.class)
+                           .register(VirtualDevice.class)
+                           .register(VirtualDeviceId.class)
+                           .register(DefaultVirtualDevice.class)
+                           .register(VirtualHost.class)
+                           .register(DefaultVirtualHost.class)
+                           .register(VirtualLink.class)
+                           .register(DefaultVirtualLink.class)
+                           .register(VirtualPort.class)
+                           .register(DefaultVirtualPort.class)
+                           .register(Device.class)
+                           .register(TunnelId.class)
+                           .register(VirtualNetworkIntent.class)
+                           .register(WallClockTimestamp.class)
+                           .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+                           .build());
+
+    /**
+     * Distributed network store service activate method.
+     */
+    @Activate
+    public void activate() {
+        idGenerator = coreService.getIdGenerator(VirtualNetworkService.VIRTUAL_NETWORK_TOPIC);
+
+        tenantIdSet = storageService.<TenantId>setBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-tenantId")
+                .withRelaxedReadConsistency()
+                .build()
+                .asDistributedSet();
+        tenantIdSet.addListener(setListener);
+
+        networkIdVirtualNetworkConsistentMap = storageService.<NetworkId, VirtualNetwork>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-networkId-virtualnetwork")
+                .withRelaxedReadConsistency()
+                .build();
+        networkIdVirtualNetworkConsistentMap.addListener(virtualNetworkMapListener);
+        networkIdVirtualNetworkMap = networkIdVirtualNetworkConsistentMap.asJavaMap();
+
+        tenantIdNetworkIdSetConsistentMap = storageService.<TenantId, Set<NetworkId>>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-tenantId-networkIds")
+                .withRelaxedReadConsistency()
+                .build();
+        tenantIdNetworkIdSetMap = tenantIdNetworkIdSetConsistentMap.asJavaMap();
+
+        deviceIdVirtualDeviceConsistentMap = storageService.<VirtualDeviceId, VirtualDevice>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-deviceId-virtualdevice")
+                .withRelaxedReadConsistency()
+                .build();
+        deviceIdVirtualDeviceConsistentMap.addListener(virtualDeviceMapListener);
+        deviceIdVirtualDeviceMap = deviceIdVirtualDeviceConsistentMap.asJavaMap();
+
+        networkIdDeviceIdSetConsistentMap = storageService.<NetworkId, Set<DeviceId>>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-networkId-deviceIds")
+                .withRelaxedReadConsistency()
+                .build();
+        networkIdDeviceIdSetMap = networkIdDeviceIdSetConsistentMap.asJavaMap();
+
+        hostIdVirtualHostConsistentMap = storageService.<HostId, VirtualHost>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-hostId-virtualhost")
+                .withRelaxedReadConsistency()
+                .build();
+        hostIdVirtualHostMap = hostIdVirtualHostConsistentMap.asJavaMap();
+
+        networkIdHostIdSetConsistentMap = storageService.<NetworkId, Set<HostId>>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-networkId-hostIds")
+                .withRelaxedReadConsistency()
+                .build();
+        networkIdHostIdSetMap = networkIdHostIdSetConsistentMap.asJavaMap();
+
+        networkIdVirtualLinkSetConsistentMap = storageService.<NetworkId, Set<VirtualLink>>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-networkId-virtuallinks")
+                .withRelaxedReadConsistency()
+                .build();
+        networkIdVirtualLinkSetMap = networkIdVirtualLinkSetConsistentMap.asJavaMap();
+
+        networkIdVirtualPortSetConsistentMap = storageService.<NetworkId, Set<VirtualPort>>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-networkId-virtualports")
+                .withRelaxedReadConsistency()
+                .build();
+        networkIdVirtualPortSetMap = networkIdVirtualPortSetConsistentMap.asJavaMap();
+
+        intentKeyTunnelIdSetConsistentMap = storageService.<Key, Set<TunnelId>>consistentMapBuilder()
+                .withSerializer(SERIALIZER)
+                .withName("onos-intentKey-tunnelIds")
+                .withRelaxedReadConsistency()
+                .build();
+        intentKeyTunnelIdSetMap = intentKeyTunnelIdSetConsistentMap.asJavaMap();
+
+        log.info("Started");
+    }
+
+    /**
+     * Distributed network store service deactivate method.
+     */
+    @Deactivate
+    public void deactivate() {
+        tenantIdSet.removeListener(setListener);
+        networkIdVirtualNetworkConsistentMap.removeListener(virtualNetworkMapListener);
+        deviceIdVirtualDeviceConsistentMap.removeListener(virtualDeviceMapListener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void addTenantId(TenantId tenantId) {
+        tenantIdSet.add(tenantId);
+    }
+
+    @Override
+    public void removeTenantId(TenantId tenantId) {
+        //Remove all the virtual networks of this tenant
+        Set<VirtualNetwork> networkIdSet = getNetworks(tenantId);
+        if (networkIdSet != null) {
+            networkIdSet.forEach(virtualNetwork -> removeNetwork(virtualNetwork.id()));
+        }
+
+        tenantIdSet.remove(tenantId);
+    }
+
+    @Override
+    public Set<TenantId> getTenantIds() {
+        return ImmutableSet.copyOf(tenantIdSet);
+    }
+
+    @Override
+    public VirtualNetwork addNetwork(TenantId tenantId) {
+
+        checkState(tenantIdSet.contains(tenantId), "The tenant has not been registered. " + tenantId.id());
+        VirtualNetwork virtualNetwork = new DefaultVirtualNetwork(genNetworkId(), tenantId);
+        //TODO update both maps in one transaction.
+        networkIdVirtualNetworkMap.put(virtualNetwork.id(), virtualNetwork);
+
+        Set<NetworkId> networkIdSet = tenantIdNetworkIdSetMap.get(tenantId);
+        if (networkIdSet == null) {
+            networkIdSet = new HashSet<>();
+        }
+        networkIdSet.add(virtualNetwork.id());
+        tenantIdNetworkIdSetMap.put(tenantId, networkIdSet);
+
+        return virtualNetwork;
+    }
+
+    /**
+     * Returns a new network identifier from a virtual network block of identifiers.
+     *
+     * @return NetworkId network identifier
+     */
+    private NetworkId genNetworkId() {
+        NetworkId networkId;
+        do {
+            networkId = NetworkId.networkId(idGenerator.getNewId());
+        } while (!networkId.isVirtualNetworkId());
+
+        return networkId;
+    }
+
+    @Override
+    public void removeNetwork(NetworkId networkId) {
+        // Make sure that the virtual network exists before attempting to remove it.
+        checkState(networkExists(networkId), "The network does not exist.");
+
+        //Remove all the devices of this network
+        Set<VirtualDevice> deviceSet = getDevices(networkId);
+        if (deviceSet != null) {
+            deviceSet.forEach(virtualDevice -> removeDevice(networkId, virtualDevice.id()));
+        }
+        //TODO update both maps in one transaction.
+
+        VirtualNetwork virtualNetwork = networkIdVirtualNetworkMap.remove(networkId);
+        if (virtualNetwork == null) {
+            return;
+        }
+        TenantId tenantId = virtualNetwork.tenantId();
+
+        Set<NetworkId> networkIdSet = new HashSet<>();
+        tenantIdNetworkIdSetMap.get(tenantId).forEach(networkId1 -> {
+            if (networkId1.id().equals(networkId.id())) {
+                networkIdSet.add(networkId1);
+            }
+        });
+
+        tenantIdNetworkIdSetMap.compute(virtualNetwork.tenantId(), (id, existingNetworkIds) -> {
+            if (existingNetworkIds == null || existingNetworkIds.isEmpty()) {
+                return new HashSet<>();
+            } else {
+                return new HashSet<>(Sets.difference(existingNetworkIds, networkIdSet));
+            }
+        });
+    }
+
+    /**
+     * Returns if the network identifier exists.
+     *
+     * @param networkId network identifier
+     * @return true if the network identifier exists, false otherwise.
+     */
+    private boolean networkExists(NetworkId networkId) {
+        checkNotNull(networkId, "The network identifier cannot be null.");
+        return (networkIdVirtualNetworkMap.containsKey(networkId));
+    }
+
+    @Override
+    public VirtualDevice addDevice(NetworkId networkId, DeviceId deviceId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+
+        Set<DeviceId> deviceIdSet = networkIdDeviceIdSetMap.get(networkId);
+        if (deviceIdSet == null) {
+            deviceIdSet = new HashSet<>();
+        }
+
+        checkState(!deviceIdSet.contains(deviceId), "The device already exists.");
+
+        VirtualDevice virtualDevice = new DefaultVirtualDevice(networkId, deviceId);
+        //TODO update both maps in one transaction.
+        deviceIdVirtualDeviceMap.put(new VirtualDeviceId(networkId, deviceId), virtualDevice);
+        deviceIdSet.add(deviceId);
+        networkIdDeviceIdSetMap.put(networkId, deviceIdSet);
+        return virtualDevice;
+    }
+
+    @Override
+    public void removeDevice(NetworkId networkId, DeviceId deviceId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        //Remove all the virtual ports of the this device
+        Set<VirtualPort> virtualPorts = getPorts(networkId, deviceId);
+        if (virtualPorts != null) {
+            virtualPorts.forEach(virtualPort -> removePort(networkId, deviceId, virtualPort.number()));
+        }
+        //TODO update both maps in one transaction.
+
+        Set<DeviceId> deviceIdSet = new HashSet<>();
+        networkIdDeviceIdSetMap.get(networkId).forEach(deviceId1 -> {
+            if (deviceId1.equals(deviceId)) {
+                deviceIdSet.add(deviceId1);
+            }
+        });
+
+        if (!deviceIdSet.isEmpty()) {
+            networkIdDeviceIdSetMap.compute(networkId, (id, existingDeviceIds) -> {
+                if (existingDeviceIds == null || existingDeviceIds.isEmpty()) {
+                    return new HashSet<>();
+                } else {
+                    return new HashSet<>(Sets.difference(existingDeviceIds, deviceIdSet));
+                }
+            });
+
+            deviceIdVirtualDeviceMap.remove(new VirtualDeviceId(networkId, deviceId));
+        }
+    }
+
+    @Override
+    public VirtualHost addHost(NetworkId networkId, HostId hostId, MacAddress mac,
+                               VlanId vlan, HostLocation location, Set<IpAddress> ips) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        checkState(virtualPortExists(networkId, location.deviceId(), location.port()),
+                "The virtual port has not been created.");
+        Set<HostId> hostIdSet = networkIdHostIdSetMap.get(networkId);
+        if (hostIdSet == null) {
+            hostIdSet = new HashSet<>();
+        }
+        VirtualHost virtualhost = new DefaultVirtualHost(networkId, hostId, mac, vlan, location, ips);
+        //TODO update both maps in one transaction.
+        hostIdVirtualHostMap.put(hostId, virtualhost);
+        hostIdSet.add(hostId);
+        networkIdHostIdSetMap.put(networkId, hostIdSet);
+        return virtualhost;
+    }
+
+    @Override
+    public void removeHost(NetworkId networkId, HostId hostId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        //TODO update both maps in one transaction.
+
+        Set<HostId> hostIdSet = new HashSet<>();
+        networkIdHostIdSetMap.get(networkId).forEach(hostId1 -> {
+            if (hostId1.equals(hostId)) {
+                hostIdSet.add(hostId1);
+            }
+        });
+
+        networkIdHostIdSetMap.compute(networkId, (id, existingHostIds) -> {
+            if (existingHostIds == null || existingHostIds.isEmpty()) {
+                return new HashSet<>();
+            } else {
+                return new HashSet<>(Sets.difference(existingHostIds, hostIdSet));
+            }
+        });
+
+        hostIdVirtualHostMap.remove(hostId);
+    }
+
+    /**
+     * Returns if the given virtual port exists.
+     *
+     * @param networkId network identifier
+     * @param deviceId virtual device Id
+     * @param portNumber virtual port number
+     * @return true if the virtual port exists, false otherwise.
+     */
+    private boolean virtualPortExists(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+        Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+        if (virtualPortSet != null) {
+            return virtualPortSet.stream().anyMatch(
+                    p -> p.element().id().equals(deviceId) &&
+                            p.number().equals(portNumber));
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public VirtualLink addLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst,
+                               Link.State state, TunnelId realizedBy) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        checkState(virtualPortExists(networkId, src.deviceId(), src.port()),
+                "The source virtual port has not been added.");
+        checkState(virtualPortExists(networkId, dst.deviceId(), dst.port()),
+                "The destination virtual port has not been added.");
+        Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(networkId);
+        if (virtualLinkSet == null) {
+            virtualLinkSet = new HashSet<>();
+        }
+
+        // validate that the link does not already exist in this network
+        checkState(getLink(networkId, src, dst) == null,
+                "The virtual link already exists");
+        checkState(getLink(networkId, src, null) == null,
+                "The source connection point has been used by another link");
+        checkState(getLink(networkId, null, dst) == null,
+                "The destination connection point has been used by another link");
+
+        VirtualLink virtualLink = DefaultVirtualLink.builder()
+                .networkId(networkId)
+                .src(src)
+                .dst(dst)
+                .state(state)
+                .tunnelId(realizedBy)
+                .build();
+
+        virtualLinkSet.add(virtualLink);
+        networkIdVirtualLinkSetMap.put(networkId, virtualLinkSet);
+        return virtualLink;
+    }
+
+    @Override
+    public void updateLink(VirtualLink virtualLink, TunnelId tunnelId, Link.State state) {
+        checkState(networkExists(virtualLink.networkId()), "The network has not been added.");
+        Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(virtualLink.networkId());
+        if (virtualLinkSet == null) {
+            virtualLinkSet = new HashSet<>();
+            networkIdVirtualLinkSetMap.put(virtualLink.networkId(), virtualLinkSet);
+            log.warn("The updated virtual link {} has not been added", virtualLink);
+            return;
+        }
+        if (!virtualLinkSet.remove(virtualLink)) {
+            log.warn("The updated virtual link {} does not exist", virtualLink);
+            return;
+        }
+
+        VirtualLink newVirtualLink = DefaultVirtualLink.builder()
+                .networkId(virtualLink.networkId())
+                .src(virtualLink.src())
+                .dst(virtualLink.dst())
+                .tunnelId(tunnelId)
+                .state(state)
+                .build();
+
+        virtualLinkSet.add(newVirtualLink);
+        networkIdVirtualLinkSetMap.put(newVirtualLink.networkId(), virtualLinkSet);
+    }
+
+    @Override
+    public VirtualLink removeLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+        checkState(networkExists(networkId), "The network has not been added.");
+
+        final VirtualLink virtualLink = getLink(networkId, src, dst);
+        if (virtualLink == null) {
+            log.warn("The removed virtual link between {} and {} does not exist", src, dst);
+            return null;
+        }
+        Set<VirtualLink> virtualLinkSet = new HashSet<>();
+        virtualLinkSet.add(virtualLink);
+
+        networkIdVirtualLinkSetMap.compute(networkId, (id, existingVirtualLinks) -> {
+            if (existingVirtualLinks == null || existingVirtualLinks.isEmpty()) {
+                return new HashSet<>();
+            } else {
+                return new HashSet<>(Sets.difference(existingVirtualLinks, virtualLinkSet));
+            }
+        });
+        return virtualLink;
+    }
+
+    @Override
+    public VirtualPort addPort(NetworkId networkId, DeviceId deviceId,
+                               PortNumber portNumber, ConnectPoint realizedBy) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+
+        if (virtualPortSet == null) {
+            virtualPortSet = new HashSet<>();
+        }
+
+        VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+        checkNotNull(device, "The device has not been created for deviceId: " + deviceId);
+
+        checkState(!virtualPortExists(networkId, deviceId, portNumber),
+                "The requested Port Number has been added.");
+
+        VirtualPort virtualPort = new DefaultVirtualPort(networkId, device,
+                                                         portNumber, realizedBy);
+        virtualPortSet.add(virtualPort);
+        networkIdVirtualPortSetMap.put(networkId, virtualPortSet);
+        notifyDelegate(new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_ADDED,
+                                               networkId, device, virtualPort));
+        return virtualPort;
+    }
+
+    @Override
+    public void bindPort(NetworkId networkId, DeviceId deviceId,
+                         PortNumber portNumber, ConnectPoint realizedBy) {
+
+        Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap
+                .get(networkId);
+
+        Optional<VirtualPort> virtualPortOptional = virtualPortSet.stream().filter(
+                p -> p.element().id().equals(deviceId) &&
+                        p.number().equals(portNumber)).findFirst();
+        checkState(virtualPortOptional.isPresent(), "The virtual port has not been added.");
+
+        VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+        checkNotNull(device, "The device has not been created for deviceId: "
+                + deviceId);
+
+        VirtualPort vPort = virtualPortOptional.get();
+        virtualPortSet.remove(vPort);
+        vPort = new DefaultVirtualPort(networkId, device, portNumber, realizedBy);
+        virtualPortSet.add(vPort);
+        networkIdVirtualPortSetMap.put(networkId, virtualPortSet);
+        notifyDelegate(new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_UPDATED,
+                                               networkId, device, vPort));
+    }
+
+    @Override
+    public void updatePortState(NetworkId networkId, DeviceId deviceId,
+                                PortNumber portNumber, boolean isEnabled) {
+        checkState(networkExists(networkId), "No network with NetworkId %s exists.", networkId);
+
+        VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+        checkNotNull(device, "No device %s exists in NetworkId: %s", deviceId, networkId);
+
+        Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+        checkNotNull(virtualPortSet, "No port has been created for NetworkId: %s", networkId);
+
+        Optional<VirtualPort> virtualPortOptional = virtualPortSet.stream().filter(
+                p -> p.element().id().equals(deviceId) &&
+                        p.number().equals(portNumber)).findFirst();
+        checkState(virtualPortOptional.isPresent(), "The virtual port has not been added.");
+
+        VirtualPort oldPort = virtualPortOptional.get();
+        if (oldPort.isEnabled() == isEnabled) {
+            log.debug("No change in port state - port not updated");
+            return;
+        }
+        VirtualPort newPort = new DefaultVirtualPort(networkId, device, portNumber, isEnabled,
+                oldPort.realizedBy());
+        virtualPortSet.remove(oldPort);
+        virtualPortSet.add(newPort);
+        networkIdVirtualPortSetMap.put(networkId, virtualPortSet);
+        notifyDelegate(new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_UPDATED,
+                                               networkId, device, newPort));
+        log.debug("port state changed from {} to {}", oldPort.isEnabled(), isEnabled);
+    }
+
+    @Override
+    public void removePort(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        VirtualDevice device = deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId));
+        checkNotNull(device, "The device has not been created for deviceId: "
+                + deviceId);
+
+        if (networkIdVirtualPortSetMap.get(networkId) == null) {
+            log.warn("No port has been created for NetworkId: {}", networkId);
+            return;
+        }
+
+        Set<VirtualPort> virtualPortSet = new HashSet<>();
+        networkIdVirtualPortSetMap.get(networkId).forEach(port -> {
+            if (port.element().id().equals(deviceId) && port.number().equals(portNumber)) {
+                virtualPortSet.add(port);
+            }
+        });
+
+        if (!virtualPortSet.isEmpty()) {
+            AtomicBoolean portRemoved = new AtomicBoolean(false);
+            networkIdVirtualPortSetMap.compute(networkId, (id, existingVirtualPorts) -> {
+                if (existingVirtualPorts == null || existingVirtualPorts.isEmpty()) {
+                    return new HashSet<>();
+                } else {
+                    portRemoved.set(true);
+                    return new HashSet<>(Sets.difference(existingVirtualPorts, virtualPortSet));
+                }
+            });
+            if (portRemoved.get()) {
+                virtualPortSet.forEach(virtualPort -> notifyDelegate(
+                        new VirtualNetworkEvent(VirtualNetworkEvent.Type.VIRTUAL_PORT_REMOVED,
+                                                networkId, device, virtualPort)
+                ));
+
+                //Remove all the virtual links connected to this virtual port
+                Set<VirtualLink> existingVirtualLinks = networkIdVirtualLinkSetMap.get(networkId);
+                if (existingVirtualLinks != null && !existingVirtualLinks.isEmpty()) {
+                    Set<VirtualLink> virtualLinkSet = new HashSet<>();
+                    ConnectPoint cp = new ConnectPoint(deviceId, portNumber);
+                    existingVirtualLinks.forEach(virtualLink -> {
+                        if (virtualLink.src().equals(cp) || virtualLink.dst().equals(cp)) {
+                            virtualLinkSet.add(virtualLink);
+                        }
+                    });
+                    virtualLinkSet.forEach(virtualLink ->
+                            removeLink(networkId, virtualLink.src(), virtualLink.dst()));
+                }
+
+                //Remove all the hosts connected to this virtual port
+                Set<HostId> hostIdSet = new HashSet<>();
+                hostIdVirtualHostMap.forEach((hostId, virtualHost) -> {
+                    if (virtualHost.location().deviceId().equals(deviceId) &&
+                            virtualHost.location().port().equals(portNumber)) {
+                        hostIdSet.add(hostId);
+                    }
+                });
+                hostIdSet.forEach(hostId -> removeHost(networkId, hostId));
+            }
+        }
+    }
+
+    @Override
+    public Set<VirtualNetwork> getNetworks(TenantId tenantId) {
+        Set<NetworkId> networkIdSet = tenantIdNetworkIdSetMap.get(tenantId);
+        Set<VirtualNetwork> virtualNetworkSet = new HashSet<>();
+        if (networkIdSet != null) {
+            networkIdSet.forEach(networkId -> {
+                if (networkIdVirtualNetworkMap.get(networkId) != null) {
+                    virtualNetworkSet.add(networkIdVirtualNetworkMap.get(networkId));
+                }
+            });
+        }
+        return ImmutableSet.copyOf(virtualNetworkSet);
+    }
+
+    @Override
+    public VirtualNetwork getNetwork(NetworkId networkId) {
+        return networkIdVirtualNetworkMap.get(networkId);
+    }
+
+    @Override
+    public Set<VirtualDevice> getDevices(NetworkId networkId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        Set<DeviceId> deviceIdSet = networkIdDeviceIdSetMap.get(networkId);
+        Set<VirtualDevice> virtualDeviceSet = new HashSet<>();
+        if (deviceIdSet != null) {
+            deviceIdSet.forEach(deviceId -> virtualDeviceSet.add(
+                    deviceIdVirtualDeviceMap.get(new VirtualDeviceId(networkId, deviceId))));
+        }
+        return ImmutableSet.copyOf(virtualDeviceSet);
+    }
+
+    @Override
+    public Set<VirtualHost> getHosts(NetworkId networkId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        Set<HostId> hostIdSet = networkIdHostIdSetMap.get(networkId);
+        Set<VirtualHost> virtualHostSet = new HashSet<>();
+        if (hostIdSet != null) {
+            hostIdSet.forEach(hostId -> virtualHostSet.add(hostIdVirtualHostMap.get(hostId)));
+        }
+        return ImmutableSet.copyOf(virtualHostSet);
+    }
+
+    @Override
+    public Set<VirtualLink> getLinks(NetworkId networkId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(networkId);
+        if (virtualLinkSet == null) {
+            virtualLinkSet = new HashSet<>();
+        }
+        return ImmutableSet.copyOf(virtualLinkSet);
+    }
+
+    @Override
+    public VirtualLink getLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+        Set<VirtualLink> virtualLinkSet = networkIdVirtualLinkSetMap.get(networkId);
+        if (virtualLinkSet == null) {
+            return null;
+        }
+
+        VirtualLink virtualLink = null;
+        for (VirtualLink link : virtualLinkSet) {
+            if (src == null && link.dst().equals(dst)) {
+                virtualLink = link;
+                break;
+            } else if (dst == null && link.src().equals(src)) {
+                virtualLink = link;
+                break;
+            } else if (link.src().equals(src) && link.dst().equals(dst)) {
+                virtualLink = link;
+                break;
+            }
+        }
+        return virtualLink;
+    }
+
+    @Override
+    public Set<VirtualPort> getPorts(NetworkId networkId, DeviceId deviceId) {
+        checkState(networkExists(networkId), "The network has not been added.");
+        Set<VirtualPort> virtualPortSet = networkIdVirtualPortSetMap.get(networkId);
+        if (virtualPortSet == null) {
+            virtualPortSet = new HashSet<>();
+        }
+
+        if (deviceId == null) {
+            return ImmutableSet.copyOf(virtualPortSet);
+        }
+
+        Set<VirtualPort> portSet = new HashSet<>();
+        virtualPortSet.forEach(virtualPort -> {
+            if (virtualPort.element().id().equals(deviceId)) {
+                portSet.add(virtualPort);
+            }
+        });
+        return ImmutableSet.copyOf(portSet);
+    }
+
+    @Override
+    public void addTunnelId(Intent intent, TunnelId tunnelId) {
+        // Add the tunnelId to the intent key set map
+        Set<TunnelId> tunnelIdSet = intentKeyTunnelIdSetMap.remove(intent.key());
+        if (tunnelIdSet == null) {
+            tunnelIdSet = new HashSet<>();
+        }
+        tunnelIdSet.add(tunnelId);
+        intentKeyTunnelIdSetMap.put(intent.key(), tunnelIdSet);
+    }
+
+    @Override
+    public Set<TunnelId> getTunnelIds(Intent intent) {
+        Set<TunnelId> tunnelIdSet = intentKeyTunnelIdSetMap.get(intent.key());
+        return tunnelIdSet == null ? new HashSet<TunnelId>() : ImmutableSet.copyOf(tunnelIdSet);
+    }
+
+    @Override
+    public void removeTunnelId(Intent intent, TunnelId tunnelId) {
+        Set<TunnelId> tunnelIdSet = new HashSet<>();
+        intentKeyTunnelIdSetMap.get(intent.key()).forEach(tunnelId1 -> {
+            if (tunnelId1.equals(tunnelId)) {
+                tunnelIdSet.add(tunnelId);
+            }
+        });
+
+        if (!tunnelIdSet.isEmpty()) {
+            intentKeyTunnelIdSetMap.compute(intent.key(), (key, existingTunnelIds) -> {
+                if (existingTunnelIds == null || existingTunnelIds.isEmpty()) {
+                    return new HashSet<>();
+                } else {
+                    return new HashSet<>(Sets.difference(existingTunnelIds, tunnelIdSet));
+                }
+            });
+        }
+    }
+
+    /**
+     * Listener class to map listener set events to the virtual network events.
+     */
+    private class InternalSetListener implements SetEventListener<TenantId> {
+        @Override
+        public void event(SetEvent<TenantId> event) {
+            VirtualNetworkEvent.Type type = null;
+            switch (event.type()) {
+                case ADD:
+                    type = VirtualNetworkEvent.Type.TENANT_REGISTERED;
+                    break;
+                case REMOVE:
+                    type = VirtualNetworkEvent.Type.TENANT_UNREGISTERED;
+                    break;
+                default:
+                    log.error("Unsupported event type: " + event.type());
+            }
+            notifyDelegate(new VirtualNetworkEvent(type, null));
+        }
+    }
+
+    /**
+     * Listener class to map listener map events to the virtual network events.
+     */
+    private class InternalMapListener<K, V> implements MapEventListener<K, V> {
+
+        private final BiFunction<MapEvent.Type, V, VirtualNetworkEvent> createEvent;
+
+        InternalMapListener(BiFunction<MapEvent.Type, V, VirtualNetworkEvent> createEvent) {
+            this.createEvent = createEvent;
+        }
+
+        @Override
+        public void event(MapEvent<K, V> event) {
+            checkNotNull(event.key());
+            VirtualNetworkEvent vnetEvent = null;
+            switch (event.type()) {
+                case INSERT:
+                    vnetEvent = createEvent.apply(event.type(), event.newValue().value());
+                    break;
+                case UPDATE:
+                    if ((event.oldValue().value() != null) && (event.newValue().value() == null)) {
+                        vnetEvent = createEvent.apply(MapEvent.Type.REMOVE, event.oldValue().value());
+                    } else {
+                        vnetEvent = createEvent.apply(event.type(), event.newValue().value());
+                    }
+                    break;
+                case REMOVE:
+                    if (event.oldValue() != null) {
+                        vnetEvent = createEvent.apply(event.type(), event.oldValue().value());
+                    }
+                    break;
+                default:
+                    log.error("Unsupported event type: " + event.type());
+            }
+            if (vnetEvent != null) {
+                notifyDelegate(vnetEvent);
+            }
+        }
+    }
+
+    /**
+     * A wrapper class to isolate device id from other virtual networks.
+     */
+
+    private static class VirtualDeviceId {
+
+        NetworkId networkId;
+        DeviceId deviceId;
+
+        public VirtualDeviceId(NetworkId networkId, DeviceId deviceId) {
+            this.networkId = networkId;
+            this.deviceId = deviceId;
+        }
+
+        public NetworkId getNetworkId() {
+            return networkId;
+        }
+
+        public DeviceId getDeviceId() {
+            return deviceId;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(networkId, deviceId);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            if (obj instanceof VirtualDeviceId) {
+                VirtualDeviceId that = (VirtualDeviceId) obj;
+                return this.deviceId.equals(that.deviceId) &&
+                        this.networkId.equals(that.networkId);
+            }
+            return false;
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualPacketStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualPacketStore.java
new file mode 100644
index 0000000..16636a5
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/DistributedVirtualPacketStore.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.MESSAGE_HANDLER_THREAD_POOL_SIZE;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+import static org.onosproject.net.packet.PacketEvent.Type.EMIT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed virtual packet store implementation allowing packets to be sent to
+ * remote instances.  Implementation is based on DistributedPacketStore class.
+ */
+@Component(immediate = true, enabled = false, service = VirtualNetworkPacketStore.class,
+        property = {
+                 MESSAGE_HANDLER_THREAD_POOL_SIZE + ":Integer=" + MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT,
+        })
+public class DistributedVirtualPacketStore
+        extends AbstractVirtualStore<PacketEvent, PacketStoreDelegate>
+        implements VirtualNetworkPacketStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final String FORMAT = "Setting: messageHandlerThreadPoolSize={}";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterCommunicationService communicationService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ComponentConfigService cfgService;
+
+    private PacketRequestTracker tracker;
+
+    private static final MessageSubject PACKET_OUT_SUBJECT =
+            new MessageSubject("virtual-packet-out");
+
+    private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
+
+    private ExecutorService messageHandlingExecutor;
+
+    /** Size of thread pool to assign message handler. */
+    private static int messageHandlerThreadPoolSize = MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT;
+
+    @Activate
+    public void activate(ComponentContext context) {
+        cfgService.registerProperties(getClass());
+
+        modified(context);
+
+        messageHandlingExecutor = Executors.newFixedThreadPool(
+                messageHandlerThreadPoolSize,
+                groupedThreads("onos/store/packet", "message-handlers", log));
+
+        communicationService.<OutboundPacketWrapper>addSubscriber(PACKET_OUT_SUBJECT,
+                SERIALIZER::decode,
+                packetWrapper -> notifyDelegate(packetWrapper.networkId,
+                                                new PacketEvent(EMIT,
+                                                                packetWrapper.outboundPacket)),
+                messageHandlingExecutor);
+
+        tracker = new PacketRequestTracker();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        cfgService.unregisterProperties(getClass(), false);
+        communicationService.removeSubscriber(PACKET_OUT_SUBJECT);
+        messageHandlingExecutor.shutdown();
+        tracker = null;
+        log.info("Stopped");
+    }
+
+    @Modified
+    public void  modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+        int newMessageHandlerThreadPoolSize;
+
+        try {
+            String s = get(properties, MESSAGE_HANDLER_THREAD_POOL_SIZE);
+
+            newMessageHandlerThreadPoolSize =
+                    isNullOrEmpty(s) ? messageHandlerThreadPoolSize : Integer.parseInt(s.trim());
+
+        } catch (NumberFormatException e) {
+            log.warn(e.getMessage());
+            newMessageHandlerThreadPoolSize = messageHandlerThreadPoolSize;
+        }
+
+        // Any change in the following parameters implies thread pool restart
+        if (newMessageHandlerThreadPoolSize != messageHandlerThreadPoolSize) {
+            setMessageHandlerThreadPoolSize(newMessageHandlerThreadPoolSize);
+            restartMessageHandlerThreadPool();
+        }
+
+        log.info(FORMAT, messageHandlerThreadPoolSize);
+    }
+
+    @Override
+    public void emit(NetworkId networkId, OutboundPacket packet) {
+        NodeId myId = clusterService.getLocalNode().id();
+        // TODO revive this when there is MastershipService support for virtual devices
+//        NodeId master = mastershipService.getMasterFor(packet.sendThrough());
+//
+//        if (master == null) {
+//            log.warn("No master found for {}", packet.sendThrough());
+//            return;
+//        }
+//
+//        log.debug("master {} found for {}", myId, packet.sendThrough());
+//        if (myId.equals(master)) {
+//            notifyDelegate(networkId, new PacketEvent(EMIT, packet));
+//            return;
+//        }
+//
+//        communicationService.unicast(packet, PACKET_OUT_SUBJECT, SERIALIZER::encode, master)
+//                            .whenComplete((r, error) -> {
+//                                if (error != null) {
+//                                    log.warn("Failed to send packet-out to {}", master, error);
+//                                }
+//                            });
+    }
+
+    @Override
+    public void requestPackets(NetworkId networkId, PacketRequest request) {
+        tracker.add(networkId, request);
+
+    }
+
+    @Override
+    public void cancelPackets(NetworkId networkId, PacketRequest request) {
+        tracker.remove(networkId, request);
+    }
+
+    @Override
+    public List<PacketRequest> existingRequests(NetworkId networkId) {
+        return tracker.requests(networkId);
+    }
+
+    private final class PacketRequestTracker {
+
+        private ConsistentMap<NetworkId, Map<RequestKey, Set<PacketRequest>>> distRequests;
+        private Map<NetworkId, Map<RequestKey, Set<PacketRequest>>> requests;
+
+        private PacketRequestTracker() {
+            distRequests = storageService.<NetworkId, Map<RequestKey, Set<PacketRequest>>>consistentMapBuilder()
+                    .withName("onos-virtual-packet-requests")
+                    .withSerializer(Serializer.using(KryoNamespace.newBuilder()
+                            .register(KryoNamespaces.API)
+                            .register(RequestKey.class)
+                            .register(NetworkId.class)
+                            .build()))
+                    .build();
+            requests = distRequests.asJavaMap();
+        }
+
+        private void add(NetworkId networkId, PacketRequest request) {
+            AtomicBoolean firstRequest = addInternal(networkId, request);
+            PacketStoreDelegate delegate = delegateMap.get(networkId);
+            if (firstRequest.get() && delegate != null) {
+                // The instance that makes the first request will push to all devices
+                delegate.requestPackets(request);
+            }
+        }
+
+        private AtomicBoolean addInternal(NetworkId networkId, PacketRequest request) {
+            AtomicBoolean firstRequest = new AtomicBoolean(false);
+            AtomicBoolean changed = new AtomicBoolean(true);
+            Map<RequestKey, Set<PacketRequest>> requestsForNetwork = getMap(networkId);
+            requestsForNetwork.compute(key(request), (s, existingRequests) -> {
+                // Reset to false just in case this is a retry due to
+                // ConcurrentModificationException
+                firstRequest.set(false);
+                if (existingRequests == null) {
+                    firstRequest.set(true);
+                    return ImmutableSet.of(request);
+                } else if (!existingRequests.contains(request)) {
+                    firstRequest.set(true);
+                    return ImmutableSet.<PacketRequest>builder()
+                                       .addAll(existingRequests)
+                                       .add(request)
+                                       .build();
+                } else {
+                    changed.set(false);
+                    return existingRequests;
+                }
+            });
+            if (changed.get()) {
+                requests.put(networkId, requestsForNetwork);
+            }
+            return firstRequest;
+        }
+
+        private void remove(NetworkId networkId, PacketRequest request) {
+            AtomicBoolean removedLast = removeInternal(networkId, request);
+            PacketStoreDelegate delegate = delegateMap.get(networkId);
+            if (removedLast.get() && delegate != null) {
+                // The instance that removes the last request will remove from all devices
+                delegate.cancelPackets(request);
+            }
+        }
+
+        private AtomicBoolean removeInternal(NetworkId networkId, PacketRequest request) {
+            AtomicBoolean removedLast = new AtomicBoolean(false);
+            AtomicBoolean changed = new AtomicBoolean(true);
+            Map<RequestKey, Set<PacketRequest>> requestsForNetwork = getMap(networkId);
+            requestsForNetwork.computeIfPresent(key(request), (s, existingRequests) -> {
+                // Reset to false just in case this is a retry due to
+                // ConcurrentModificationException
+                removedLast.set(false);
+                if (existingRequests.contains(request)) {
+                    Set<PacketRequest> newRequests = Sets.newHashSet(existingRequests);
+                    newRequests.remove(request);
+                    if (newRequests.size() > 0) {
+                        return ImmutableSet.copyOf(newRequests);
+                    } else {
+                        removedLast.set(true);
+                        return null;
+                    }
+                } else {
+                    changed.set(false);
+                    return existingRequests;
+                }
+            });
+            if (changed.get()) {
+                requests.put(networkId, requestsForNetwork);
+            }
+            return removedLast;
+        }
+
+        private List<PacketRequest> requests(NetworkId networkId) {
+            Map<RequestKey, Set<PacketRequest>> requestsForNetwork = getMap(networkId);
+            List<PacketRequest> list = Lists.newArrayList();
+            requestsForNetwork.values().forEach(v -> list.addAll(v));
+            list.sort((o1, o2) -> o1.priority().priorityValue() - o2.priority().priorityValue());
+            return list;
+        }
+
+        /*
+         * Gets PacketRequests for specified networkId.
+         */
+        private Map<RequestKey, Set<PacketRequest>> getMap(NetworkId networkId) {
+            return requests.computeIfAbsent(networkId, networkId1 -> {
+                        log.debug("Creating new map for {}", networkId1);
+                        Map newMap = Maps.newHashMap();
+                        return newMap;
+                    });
+        }
+    }
+
+    /**
+     * Creates a new request key from a packet request.
+     *
+     * @param request packet request
+     * @return request key
+     */
+    private static RequestKey key(PacketRequest request) {
+        return new RequestKey(request.selector(), request.priority());
+    }
+
+    /**
+     * Key of a packet request.
+     */
+    private static final class RequestKey {
+        private final TrafficSelector selector;
+        private final PacketPriority priority;
+
+        private RequestKey(TrafficSelector selector, PacketPriority priority) {
+            this.selector = selector;
+            this.priority = priority;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(selector, priority);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == this) {
+                return true;
+            }
+
+            if (!(other instanceof RequestKey)) {
+                return false;
+            }
+
+            RequestKey that = (RequestKey) other;
+
+            return Objects.equals(selector, that.selector) &&
+                    Objects.equals(priority, that.priority);
+        }
+    }
+
+    private static OutboundPacketWrapper wrapper(NetworkId networkId, OutboundPacket outboundPacket) {
+        return new OutboundPacketWrapper(networkId, outboundPacket);
+    }
+
+    /*
+     * OutboundPacket in
+     */
+    private static final class OutboundPacketWrapper {
+        private NetworkId networkId;
+        private OutboundPacket outboundPacket;
+
+        private OutboundPacketWrapper(NetworkId networkId, OutboundPacket outboundPacket) {
+            this.networkId = networkId;
+            this.outboundPacket = outboundPacket;
+        }
+
+    }
+
+    /**
+     * Sets thread pool size of message handler.
+     *
+     * @param poolSize
+     */
+    private void setMessageHandlerThreadPoolSize(int poolSize) {
+        checkArgument(poolSize >= 0, "Message handler pool size must be 0 or more");
+        messageHandlerThreadPoolSize = poolSize;
+    }
+
+    /**
+     * Restarts thread pool of message handler.
+     */
+    private void restartMessageHandlerThreadPool() {
+        ExecutorService prevExecutor = messageHandlingExecutor;
+        messageHandlingExecutor = newFixedThreadPool(getMessageHandlerThreadPoolSize(),
+                                                     groupedThreads("DistPktStore", "messageHandling-%d", log));
+        prevExecutor.shutdown();
+    }
+
+    /**
+     * Gets current thread pool size of message handler.
+     *
+     * @return messageHandlerThreadPoolSize
+     */
+    private int getMessageHandlerThreadPoolSize() {
+        return messageHandlerThreadPoolSize;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/MeterData.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/MeterData.java
new file mode 100644
index 0000000..c014505
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/MeterData.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import org.onosproject.cluster.NodeId;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterFailReason;
+
+import java.util.Optional;
+
+/**
+ * A class representing the meter information stored in the meter store.
+ */
+public class MeterData {
+
+    private final Meter meter;
+    private final Optional<MeterFailReason> reason;
+    private final NodeId origin;
+
+    public MeterData(Meter meter, MeterFailReason reason, NodeId origin) {
+        this.meter = meter;
+        this.reason = Optional.ofNullable(reason);
+        this.origin = origin;
+    }
+
+    public Meter meter() {
+        return meter;
+    }
+
+    public Optional<MeterFailReason> reason() {
+        return this.reason;
+    }
+
+    public NodeId origin() {
+        return this.origin;
+    }
+
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/OsgiPropertyConstants.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/OsgiPropertyConstants.java
new file mode 100644
index 0000000..39bc5b0
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/OsgiPropertyConstants.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+/**
+ * Constants for default values of configurable properties.
+ */
+public final class OsgiPropertyConstants {
+
+    private OsgiPropertyConstants() {}
+
+    public static final String MESSAGE_HANDLER_THREAD_POOL_SIZE = "messageHandlerThreadPoolSize";
+    public static final int MESSAGE_HANDLER_THREAD_POOL_SIZE_DEFAULT = 4;
+
+    public static final String BACKUP_PERIOD_MILLIS = "backupPeriod";
+    public static final int BACKUP_PERIOD_MILLIS_DEFAULT = 2000;
+
+    public static final String PERSISTENCE_ENABLED = "persistenceEnabled";
+    public static final boolean PERSISTENCE_ENABLED_DEFAULT = false;
+
+    public static final String PENDING_FUTURE_TIMEOUT_MINUTES = "pendingFutureTimeoutMinutes";
+    public static final int PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT = 5;
+
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowObjectiveStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowObjectiveStore.java
new file mode 100644
index 0000000..4ef268e
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowObjectiveStore.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Maps;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.net.behaviour.DefaultNextGroup;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.flowobjective.FlowObjectiveStoreDelegate;
+import org.onosproject.net.flowobjective.ObjectiveEvent;
+import org.onosproject.store.service.AtomicCounter;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Single instance implementation of store to manage
+ * the inventory of created next groups for virtual network.
+ */
+@Component(immediate = true, service = VirtualNetworkFlowObjectiveStore.class)
+public class SimpleVirtualFlowObjectiveStore
+        extends AbstractVirtualStore<ObjectiveEvent, FlowObjectiveStoreDelegate>
+        implements VirtualNetworkFlowObjectiveStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private ConcurrentMap<NetworkId, ConcurrentMap<Integer, byte[]>> nextGroupsMap;
+
+    private AtomicCounter nextIds;
+
+    // event queue to separate map-listener threads from event-handler threads (tpool)
+    private BlockingQueue<VirtualObjectiveEvent> eventQ;
+    private ExecutorService tpool;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected StorageService storageService;
+
+    @Activate
+    public void activate() {
+        tpool = Executors.newFixedThreadPool(4, groupedThreads("onos/virtual/flobj-notifier", "%d", log));
+        eventQ = new LinkedBlockingQueue<>();
+        tpool.execute(new FlowObjectiveNotifier());
+
+        initNextGroupsMap();
+
+        nextIds = storageService.getAtomicCounter("next-objective-counter");
+        log.info("Started");
+    }
+
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    protected void initNextGroupsMap() {
+        nextGroupsMap = Maps.newConcurrentMap();
+    }
+
+    protected void updateNextGroupsMap(NetworkId networkId,
+                                       ConcurrentMap<Integer, byte[]> nextGroups) {
+    }
+
+    protected ConcurrentMap<Integer, byte[]> getNextGroups(NetworkId networkId) {
+        nextGroupsMap.computeIfAbsent(networkId, n -> Maps.newConcurrentMap());
+        return nextGroupsMap.get(networkId);
+    }
+
+    @Override
+    public void putNextGroup(NetworkId networkId, Integer nextId, NextGroup group) {
+        ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+        nextGroups.put(nextId, group.data());
+        updateNextGroupsMap(networkId, nextGroups);
+
+        eventQ.add(new VirtualObjectiveEvent(networkId, ObjectiveEvent.Type.ADD, nextId));
+    }
+
+    @Override
+    public NextGroup getNextGroup(NetworkId networkId, Integer nextId) {
+        ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+        byte[] groupData = nextGroups.get(nextId);
+        if (groupData != null) {
+            return new DefaultNextGroup(groupData);
+        }
+        return null;
+    }
+
+    @Override
+    public NextGroup removeNextGroup(NetworkId networkId, Integer nextId) {
+        ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+        byte[] nextGroup = nextGroups.remove(nextId);
+        updateNextGroupsMap(networkId, nextGroups);
+
+        eventQ.add(new VirtualObjectiveEvent(networkId, ObjectiveEvent.Type.REMOVE, nextId));
+
+        return new DefaultNextGroup(nextGroup);
+    }
+
+    @Override
+    public Map<Integer, NextGroup> getAllGroups(NetworkId networkId) {
+        ConcurrentMap<Integer, byte[]> nextGroups = getNextGroups(networkId);
+
+        Map<Integer, NextGroup> nextGroupMappings = new HashMap<>();
+        for (int key : nextGroups.keySet()) {
+            NextGroup nextGroup = getNextGroup(networkId, key);
+            if (nextGroup != null) {
+                nextGroupMappings.put(key, nextGroup);
+            }
+        }
+        return nextGroupMappings;
+    }
+
+    @Override
+    public int allocateNextId(NetworkId networkId) {
+        return (int) nextIds.incrementAndGet();
+    }
+
+    private class FlowObjectiveNotifier implements Runnable {
+        @Override
+        public void run() {
+            try {
+                while (!Thread.currentThread().isInterrupted()) {
+                    VirtualObjectiveEvent vEvent = eventQ.take();
+                    notifyDelegate(vEvent.networkId(), vEvent);
+                }
+            } catch (InterruptedException ex) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    private class VirtualObjectiveEvent extends ObjectiveEvent {
+        NetworkId networkId;
+
+        public VirtualObjectiveEvent(NetworkId networkId, Type type,
+                                     Integer objective) {
+            super(type, objective);
+            this.networkId = networkId;
+        }
+
+        NetworkId networkId() {
+            return networkId;
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowRuleStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowRuleStore.java
new file mode 100644
index 0000000..1fc5cde
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualFlowRuleStore.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.SettableFuture;
+import org.onlab.util.Tools;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleStoreDelegate;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.PENDING_FUTURE_TIMEOUT_MINUTES;
+import static org.onosproject.incubator.net.virtual.store.impl.OsgiPropertyConstants.PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual network flow rule store to manage inventory of
+ * virtual flow rules using trivial in-memory implementation.
+ */
+//TODO: support distributed flowrule store for virtual networks
+
+@Component(immediate = true, service = VirtualNetworkFlowRuleStore.class,
+        property = {
+                 PENDING_FUTURE_TIMEOUT_MINUTES + ":Integer=" + PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT,
+        })
+public class SimpleVirtualFlowRuleStore
+        extends AbstractVirtualStore<FlowRuleBatchEvent, FlowRuleStoreDelegate>
+        implements VirtualNetworkFlowRuleStore {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected StorageService storageService;
+
+    private final ConcurrentMap<NetworkId,
+            ConcurrentMap<DeviceId, ConcurrentMap<FlowId, List<StoredFlowEntry>>>>
+            flowEntries = new ConcurrentHashMap<>();
+
+
+    private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+    /** Expiration time after an entry is created that it should be automatically removed. */
+    private int pendingFutureTimeoutMinutes = PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT;
+
+    private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
+            CacheBuilder.newBuilder()
+                    .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+                    .removalListener(new TimeoutFuture())
+                    .build();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        flowEntries.clear();
+        log.info("Stopped");
+    }
+
+    @Modified
+    public void modified(ComponentContext context) {
+
+        readComponentConfiguration(context);
+
+        // Reset Cache and copy all.
+        Cache<Integer, SettableFuture<CompletedBatchOperation>> prevFutures = pendingFutures;
+        pendingFutures = CacheBuilder.newBuilder()
+                .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+                .removalListener(new TimeoutFuture())
+                .build();
+
+        pendingFutures.putAll(prevFutures.asMap());
+    }
+
+    /**
+     * Extracts properties from the component configuration context.
+     *
+     * @param context the component context
+     */
+    private void readComponentConfiguration(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+
+        Integer newPendingFutureTimeoutMinutes =
+                Tools.getIntegerProperty(properties, "pendingFutureTimeoutMinutes");
+        if (newPendingFutureTimeoutMinutes == null) {
+            pendingFutureTimeoutMinutes = PENDING_FUTURE_TIMEOUT_MINUTES_DEFAULT;
+            log.info("Pending future timeout is not configured, " +
+                             "using current value of {}", pendingFutureTimeoutMinutes);
+        } else {
+            pendingFutureTimeoutMinutes = newPendingFutureTimeoutMinutes;
+            log.info("Configured. Pending future timeout is configured to {}",
+                     pendingFutureTimeoutMinutes);
+        }
+    }
+
+    @Override
+    public int getFlowRuleCount(NetworkId networkId) {
+        int sum = 0;
+
+        if (flowEntries.get(networkId) == null) {
+            return 0;
+        }
+
+        for (ConcurrentMap<FlowId, List<StoredFlowEntry>> ft :
+                flowEntries.get(networkId).values()) {
+            for (List<StoredFlowEntry> fes : ft.values()) {
+                sum += fes.size();
+            }
+        }
+        return sum;
+    }
+
+    @Override
+    public FlowEntry getFlowEntry(NetworkId networkId, FlowRule rule) {
+        return getFlowEntryInternal(networkId, rule.deviceId(), rule);
+    }
+
+    @Override
+    public Iterable<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
+        return FluentIterable.from(getFlowTable(networkId, deviceId).values())
+                .transformAndConcat(Collections::unmodifiableList);
+    }
+
+    private void storeFlowRule(NetworkId networkId, FlowRule rule) {
+        storeFlowRuleInternal(networkId, rule);
+    }
+
+    @Override
+    public void storeBatch(NetworkId networkId, FlowRuleBatchOperation batchOperation) {
+        List<FlowRuleBatchEntry> toAdd = new ArrayList<>();
+        List<FlowRuleBatchEntry> toRemove = new ArrayList<>();
+
+        for (FlowRuleBatchEntry entry : batchOperation.getOperations()) {
+            final FlowRule flowRule = entry.target();
+            if (entry.operator().equals(FlowRuleBatchEntry.FlowRuleOperation.ADD)) {
+                if (!getFlowEntries(networkId, flowRule.deviceId(),
+                                    flowRule.id()).contains(flowRule)) {
+                    storeFlowRule(networkId, flowRule);
+                    toAdd.add(entry);
+                }
+            } else if (entry.operator().equals(FlowRuleBatchEntry.FlowRuleOperation.REMOVE)) {
+                if (getFlowEntries(networkId, flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+                    deleteFlowRule(networkId, flowRule);
+                    toRemove.add(entry);
+                }
+            } else {
+                throw new UnsupportedOperationException("Unsupported operation type");
+            }
+        }
+
+        if (toAdd.isEmpty() && toRemove.isEmpty()) {
+            notifyDelegate(networkId, FlowRuleBatchEvent.completed(
+                    new FlowRuleBatchRequest(batchOperation.id(), Collections.emptySet()),
+                    new CompletedBatchOperation(true, Collections.emptySet(),
+                                                batchOperation.deviceId())));
+            return;
+        }
+
+        SettableFuture<CompletedBatchOperation> r = SettableFuture.create();
+        final int futureId = localBatchIdGen.incrementAndGet();
+
+        pendingFutures.put(futureId, r);
+
+        toAdd.addAll(toRemove);
+        notifyDelegate(networkId, FlowRuleBatchEvent.requested(
+                new FlowRuleBatchRequest(batchOperation.id(),
+                                         Sets.newHashSet(toAdd)), batchOperation.deviceId()));
+
+    }
+
+    @Override
+    public void batchOperationComplete(NetworkId networkId, FlowRuleBatchEvent event) {
+        final Long batchId = event.subject().batchId();
+        SettableFuture<CompletedBatchOperation> future
+                = pendingFutures.getIfPresent(batchId);
+        if (future != null) {
+            future.set(event.result());
+            pendingFutures.invalidate(batchId);
+        }
+        notifyDelegate(networkId, event);
+    }
+
+    @Override
+    public void deleteFlowRule(NetworkId networkId, FlowRule rule) {
+        List<StoredFlowEntry> entries = getFlowEntries(networkId, rule.deviceId(), rule.id());
+
+        synchronized (entries) {
+            for (StoredFlowEntry entry : entries) {
+                if (entry.equals(rule)) {
+                    synchronized (entry) {
+                        entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public FlowRuleEvent addOrUpdateFlowRule(NetworkId networkId, FlowEntry rule) {
+        // check if this new rule is an update to an existing entry
+        List<StoredFlowEntry> entries = getFlowEntries(networkId, rule.deviceId(), rule.id());
+        synchronized (entries) {
+            for (StoredFlowEntry stored : entries) {
+                if (stored.equals(rule)) {
+                    synchronized (stored) {
+                        //FIXME modification of "stored" flow entry outside of flow table
+                        stored.setBytes(rule.bytes());
+                        stored.setLife(rule.life());
+                        stored.setPackets(rule.packets());
+                        if (stored.state() == FlowEntry.FlowEntryState.PENDING_ADD) {
+                            stored.setState(FlowEntry.FlowEntryState.ADDED);
+                            // TODO: Do we need to change `rule` state?
+                            return new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, rule);
+                        }
+                        return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+                    }
+                }
+            }
+        }
+
+        // should not reach here
+        // storeFlowRule was expected to be called
+        log.error("FlowRule was not found in store {} to update", rule);
+
+        return null;
+    }
+
+    @Override
+    public FlowRuleEvent removeFlowRule(NetworkId networkId, FlowEntry rule) {
+        // This is where one could mark a rule as removed and still keep it in the store.
+        final DeviceId did = rule.deviceId();
+
+        List<StoredFlowEntry> entries = getFlowEntries(networkId, did, rule.id());
+        synchronized (entries) {
+            if (entries.remove(rule)) {
+                return new FlowRuleEvent(RULE_REMOVED, rule);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public FlowRuleEvent pendingFlowRule(NetworkId networkId, FlowEntry rule) {
+        List<StoredFlowEntry> entries = getFlowEntries(networkId, rule.deviceId(), rule.id());
+        synchronized (entries) {
+            for (StoredFlowEntry entry : entries) {
+                if (entry.equals(rule) &&
+                        entry.state() != FlowEntry.FlowEntryState.PENDING_ADD) {
+                    synchronized (entry) {
+                        entry.setState(FlowEntry.FlowEntryState.PENDING_ADD);
+                        return new FlowRuleEvent(FlowRuleEvent.Type.RULE_UPDATED, rule);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void purgeFlowRule(NetworkId networkId, DeviceId deviceId) {
+        flowEntries.get(networkId).remove(deviceId);
+    }
+
+    @Override
+    public void purgeFlowRules(NetworkId networkId) {
+        flowEntries.get(networkId).clear();
+    }
+
+    @Override
+    public FlowRuleEvent
+    updateTableStatistics(NetworkId networkId, DeviceId deviceId, List<TableStatisticsEntry> tableStats) {
+        //TODO: Table operations are not supported yet
+        return null;
+    }
+
+    @Override
+    public Iterable<TableStatisticsEntry>
+    getTableStatistics(NetworkId networkId, DeviceId deviceId) {
+        //TODO: Table operations are not supported yet
+        return null;
+    }
+
+    /**
+     * Returns the flow table for specified device.
+     *
+     * @param networkId identifier of the virtual network
+     * @param deviceId identifier of the virtual device
+     * @return Map representing Flow Table of given device.
+     */
+    private ConcurrentMap<FlowId, List<StoredFlowEntry>>
+    getFlowTable(NetworkId networkId, DeviceId deviceId) {
+        return flowEntries
+                .computeIfAbsent(networkId, n -> new ConcurrentHashMap<>())
+                .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+    }
+
+    private List<StoredFlowEntry>
+    getFlowEntries(NetworkId networkId, DeviceId deviceId, FlowId flowId) {
+        final ConcurrentMap<FlowId, List<StoredFlowEntry>> flowTable
+                = getFlowTable(networkId, deviceId);
+
+        List<StoredFlowEntry> r = flowTable.get(flowId);
+        if (r == null) {
+            final List<StoredFlowEntry> concurrentlyAdded;
+            r = new CopyOnWriteArrayList<>();
+            concurrentlyAdded = flowTable.putIfAbsent(flowId, r);
+            if (concurrentlyAdded != null) {
+                return concurrentlyAdded;
+            }
+        }
+        return r;
+    }
+
+    private FlowEntry
+    getFlowEntryInternal(NetworkId networkId, DeviceId deviceId, FlowRule rule) {
+        List<StoredFlowEntry> fes = getFlowEntries(networkId, deviceId, rule.id());
+        for (StoredFlowEntry fe : fes) {
+            if (fe.equals(rule)) {
+                return fe;
+            }
+        }
+        return null;
+    }
+
+    private void storeFlowRuleInternal(NetworkId networkId, FlowRule rule) {
+        StoredFlowEntry f = new DefaultFlowEntry(rule);
+        final DeviceId did = f.deviceId();
+        final FlowId fid = f.id();
+        List<StoredFlowEntry> existing = getFlowEntries(networkId, did, fid);
+        synchronized (existing) {
+            for (StoredFlowEntry fe : existing) {
+                if (fe.equals(rule)) {
+                    // was already there? ignore
+                    return;
+                }
+            }
+            // new flow rule added
+            existing.add(f);
+        }
+    }
+
+    private static final class TimeoutFuture
+            implements RemovalListener<Integer, SettableFuture<CompletedBatchOperation>> {
+        @Override
+        public void onRemoval(RemovalNotification<Integer,
+                SettableFuture<CompletedBatchOperation>> notification) {
+            // wrapping in ExecutionException to support Future.get
+            if (notification.wasEvicted()) {
+                notification.getValue()
+                        .setException(new ExecutionException("Timed out",
+                                                             new TimeoutException()));
+            }
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualGroupStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualGroupStore.java
new file mode 100644
index 0000000..3c696ce
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualGroupStore.java
@@ -0,0 +1,764 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+import org.onosproject.core.GroupId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkGroupStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages inventory of virtual group entries using trivial in-memory implementation.
+ */
+@Component(immediate = true, service = VirtualNetworkGroupStore.class)
+public class SimpleVirtualGroupStore
+        extends AbstractVirtualStore<GroupEvent, GroupStoreDelegate>
+        implements VirtualNetworkGroupStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private final int dummyId = 0xffffffff;
+    private final GroupId dummyGroupId = new GroupId(dummyId);
+
+    // inner Map is per device group table
+    private final ConcurrentMap<NetworkId,
+            ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>>
+            groupEntriesByKey = new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<NetworkId,
+            ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>>
+            groupEntriesById = new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<NetworkId,
+            ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>>
+            pendingGroupEntriesByKey = new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<NetworkId,
+            ConcurrentMap<DeviceId, ConcurrentMap<GroupId, Group>>>
+            extraneousGroupEntriesById = new ConcurrentHashMap<>();
+
+    private final ConcurrentMap<NetworkId, HashMap<DeviceId, Boolean>>
+            deviceAuditStatus = new ConcurrentHashMap<>();
+
+    private final AtomicInteger groupIdGen = new AtomicInteger();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        groupEntriesByKey.clear();
+        groupEntriesById.clear();
+        log.info("Stopped");
+    }
+
+    /**
+     * Returns the group key table for specified device.
+     *
+     * @param networkId identifier of the virtual network
+     * @param deviceId identifier of the device
+     * @return Map representing group key table of given device.
+     */
+    private ConcurrentMap<GroupKey, StoredGroupEntry>
+    getGroupKeyTable(NetworkId networkId, DeviceId deviceId) {
+        groupEntriesByKey.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+        return groupEntriesByKey.get(networkId)
+                .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+    }
+
+    /**
+     * Returns the group id table for specified device.
+     *
+     * @param networkId identifier of the virtual network
+     * @param deviceId identifier of the device
+     * @return Map representing group key table of given device.
+     */
+    private ConcurrentMap<GroupId, StoredGroupEntry>
+    getGroupIdTable(NetworkId networkId, DeviceId deviceId) {
+        groupEntriesById.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+        return groupEntriesById.get(networkId)
+                .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+    }
+
+    /**
+     * Returns the pending group key table for specified device.
+     *
+     * @param networkId identifier of the virtual network
+     * @param deviceId identifier of the device
+     * @return Map representing group key table of given device.
+     */
+    private ConcurrentMap<GroupKey, StoredGroupEntry>
+    getPendingGroupKeyTable(NetworkId networkId, DeviceId deviceId) {
+        pendingGroupEntriesByKey.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+        return pendingGroupEntriesByKey.get(networkId)
+                .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+    }
+
+    /**
+     * Returns the extraneous group id table for specified device.
+     *
+     * @param networkId identifier of the virtual network
+     * @param deviceId identifier of the device
+     * @return Map representing group key table of given device.
+     */
+    private ConcurrentMap<GroupId, Group>
+    getExtraneousGroupIdTable(NetworkId networkId, DeviceId deviceId) {
+        extraneousGroupEntriesById.computeIfAbsent(networkId, n -> new ConcurrentHashMap<>());
+        return extraneousGroupEntriesById.get(networkId)
+                .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
+    }
+
+    @Override
+    public int getGroupCount(NetworkId networkId, DeviceId deviceId) {
+        return (groupEntriesByKey.get(networkId).get(deviceId) != null) ?
+                groupEntriesByKey.get(networkId).get(deviceId).size() : 0;
+    }
+
+    @Override
+    public Iterable<Group> getGroups(NetworkId networkId, DeviceId deviceId) {
+        // flatten and make iterator unmodifiable
+        return FluentIterable.from(getGroupKeyTable(networkId, deviceId).values())
+                .transform(input -> input);
+    }
+
+    @Override
+    public Group getGroup(NetworkId networkId, DeviceId deviceId, GroupKey appCookie) {
+        if (groupEntriesByKey.get(networkId) != null &&
+                groupEntriesByKey.get(networkId).get(deviceId) != null) {
+            return groupEntriesByKey.get(networkId).get(deviceId).get(appCookie);
+        }
+        return null;
+    }
+
+    @Override
+    public Group getGroup(NetworkId networkId, DeviceId deviceId, GroupId groupId) {
+        if (groupEntriesById.get(networkId) != null &&
+                groupEntriesById.get(networkId).get(deviceId) != null) {
+            return groupEntriesById.get(networkId).get(deviceId).get(groupId);
+        }
+        return null;
+    }
+
+    private int getFreeGroupIdValue(NetworkId networkId, DeviceId deviceId) {
+        int freeId = groupIdGen.incrementAndGet();
+
+        while (true) {
+            Group existing = null;
+            if (groupEntriesById.get(networkId) != null &&
+                    groupEntriesById.get(networkId).get(deviceId) != null) {
+                existing = groupEntriesById.get(networkId).get(deviceId)
+                                .get(new GroupId(freeId));
+            }
+
+            if (existing == null) {
+                if (extraneousGroupEntriesById.get(networkId) != null &&
+                        extraneousGroupEntriesById.get(networkId).get(deviceId) != null) {
+                    existing = extraneousGroupEntriesById.get(networkId).get(deviceId)
+                                    .get(new GroupId(freeId));
+                }
+            }
+
+            if (existing != null) {
+                freeId = groupIdGen.incrementAndGet();
+            } else {
+                break;
+            }
+        }
+        return freeId;
+    }
+
+    @Override
+    public void storeGroupDescription(NetworkId networkId, GroupDescription groupDesc) {
+        // Check if a group is existing with the same key
+        if (getGroup(networkId, groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+            return;
+        }
+
+        if (deviceAuditStatus.get(networkId) == null ||
+                deviceAuditStatus.get(networkId).get(groupDesc.deviceId()) == null) {
+            // Device group audit has not completed yet
+            // Add this group description to pending group key table
+            // Create a group entry object with Dummy Group ID
+            StoredGroupEntry group = new DefaultGroup(dummyGroupId, groupDesc);
+            group.setState(Group.GroupState.WAITING_AUDIT_COMPLETE);
+            ConcurrentMap<GroupKey, StoredGroupEntry> pendingKeyTable =
+                    getPendingGroupKeyTable(networkId, groupDesc.deviceId());
+            pendingKeyTable.put(groupDesc.appCookie(), group);
+            return;
+        }
+
+        storeGroupDescriptionInternal(networkId, groupDesc);
+    }
+
+    private void storeGroupDescriptionInternal(NetworkId networkId,
+                                               GroupDescription groupDesc) {
+        // Check if a group is existing with the same key
+        if (getGroup(networkId, groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+            return;
+        }
+
+        GroupId id = null;
+        if (groupDesc.givenGroupId() == null) {
+            // Get a new group identifier
+            id = new GroupId(getFreeGroupIdValue(networkId, groupDesc.deviceId()));
+        } else {
+            id = new GroupId(groupDesc.givenGroupId());
+        }
+        // Create a group entry object
+        StoredGroupEntry group = new DefaultGroup(id, groupDesc);
+        // Insert the newly created group entry into concurrent key and id maps
+        ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                getGroupKeyTable(networkId, groupDesc.deviceId());
+        keyTable.put(groupDesc.appCookie(), group);
+        ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                getGroupIdTable(networkId, groupDesc.deviceId());
+        idTable.put(id, group);
+        notifyDelegate(networkId, new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+                                      group));
+    }
+
+    @Override
+    public void updateGroupDescription(NetworkId networkId, DeviceId deviceId,
+                                       GroupKey oldAppCookie, UpdateType type,
+                                       GroupBuckets newBuckets, GroupKey newAppCookie) {
+        // Check if a group is existing with the provided key
+        Group oldGroup = getGroup(networkId, deviceId, oldAppCookie);
+        if (oldGroup == null) {
+            return;
+        }
+
+        List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup,
+                                                               type,
+                                                               newBuckets);
+        if (newBucketList != null) {
+            // Create a new group object from the old group
+            GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+            GroupKey newCookie = (newAppCookie != null) ? newAppCookie : oldAppCookie;
+            GroupDescription updatedGroupDesc = new DefaultGroupDescription(
+                    oldGroup.deviceId(),
+                    oldGroup.type(),
+                    updatedBuckets,
+                    newCookie,
+                    oldGroup.givenGroupId(),
+                    oldGroup.appId());
+            StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(),
+                                                         updatedGroupDesc);
+            newGroup.setState(Group.GroupState.PENDING_UPDATE);
+            newGroup.setLife(oldGroup.life());
+            newGroup.setPackets(oldGroup.packets());
+            newGroup.setBytes(oldGroup.bytes());
+
+            // Remove the old entry from maps and add new entry using new key
+            ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                    getGroupKeyTable(networkId, oldGroup.deviceId());
+            ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                    getGroupIdTable(networkId, oldGroup.deviceId());
+            keyTable.remove(oldGroup.appCookie());
+            idTable.remove(oldGroup.id());
+            keyTable.put(newGroup.appCookie(), newGroup);
+            idTable.put(newGroup.id(), newGroup);
+            notifyDelegate(networkId,
+                           new GroupEvent(GroupEvent.Type.GROUP_UPDATE_REQUESTED,
+                                          newGroup));
+        }
+
+    }
+
+    private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
+                                                   UpdateType type,
+                                                   GroupBuckets buckets) {
+        if (type == UpdateType.SET) {
+            return buckets.buckets();
+        }
+
+        List<GroupBucket> oldBuckets = oldGroup.buckets().buckets();
+        List<GroupBucket> updatedBucketList = new ArrayList<>();
+        boolean groupDescUpdated = false;
+
+        if (type == UpdateType.ADD) {
+            List<GroupBucket> newBuckets = buckets.buckets();
+
+            // Add old buckets that will not be updated and check if any will be updated.
+            for (GroupBucket oldBucket : oldBuckets) {
+                int newBucketIndex = newBuckets.indexOf(oldBucket);
+
+                if (newBucketIndex != -1) {
+                    GroupBucket newBucket = newBuckets.get(newBucketIndex);
+                    if (!newBucket.hasSameParameters(oldBucket)) {
+                        // Bucket will be updated
+                        groupDescUpdated = true;
+                    }
+                } else {
+                    // Old bucket will remain the same - add it.
+                    updatedBucketList.add(oldBucket);
+                }
+            }
+
+            // Add all new buckets
+            updatedBucketList.addAll(newBuckets);
+            if (!oldBuckets.containsAll(newBuckets)) {
+                groupDescUpdated = true;
+            }
+
+        } else if (type == UpdateType.REMOVE) {
+            List<GroupBucket> bucketsToRemove = buckets.buckets();
+
+            // Check which old buckets should remain
+            for (GroupBucket oldBucket : oldBuckets) {
+                if (!bucketsToRemove.contains(oldBucket)) {
+                    updatedBucketList.add(oldBucket);
+                } else {
+                    groupDescUpdated = true;
+                }
+            }
+        }
+
+        if (groupDescUpdated) {
+            return updatedBucketList;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void deleteGroupDescription(NetworkId networkId, DeviceId deviceId,
+                                       GroupKey appCookie) {
+        // Check if a group is existing with the provided key
+        StoredGroupEntry existing = null;
+        if (groupEntriesByKey.get(networkId) != null &&
+                groupEntriesByKey.get(networkId).get(deviceId) != null) {
+            existing = groupEntriesByKey.get(networkId).get(deviceId).get(appCookie);
+        }
+
+        if (existing == null) {
+            return;
+        }
+
+        synchronized (existing) {
+            existing.setState(Group.GroupState.PENDING_DELETE);
+        }
+        notifyDelegate(networkId,
+                       new GroupEvent(GroupEvent.Type.GROUP_REMOVE_REQUESTED, existing));
+    }
+
+    @Override
+    public void addOrUpdateGroupEntry(NetworkId networkId, Group group) {
+        // check if this new entry is an update to an existing entry
+        StoredGroupEntry existing = null;
+
+        if (groupEntriesById.get(networkId) != null &&
+                groupEntriesById.get(networkId).get(group.deviceId()) != null) {
+            existing = groupEntriesById
+                    .get(networkId)
+                    .get(group.deviceId())
+                    .get(group.id());
+        }
+
+        GroupEvent event = null;
+
+        if (existing != null) {
+            synchronized (existing) {
+                for (GroupBucket bucket:group.buckets().buckets()) {
+                    Optional<GroupBucket> matchingBucket =
+                            existing.buckets().buckets()
+                                    .stream()
+                                    .filter((existingBucket) -> (existingBucket.equals(bucket)))
+                                    .findFirst();
+                    if (matchingBucket.isPresent()) {
+                        ((StoredGroupBucketEntry) matchingBucket.
+                                get()).setPackets(bucket.packets());
+                        ((StoredGroupBucketEntry) matchingBucket.
+                                get()).setBytes(bucket.bytes());
+                    } else {
+                        log.warn("addOrUpdateGroupEntry: No matching "
+                                         + "buckets to update stats");
+                    }
+                }
+                existing.setLife(group.life());
+                existing.setPackets(group.packets());
+                existing.setBytes(group.bytes());
+                if (existing.state() == Group.GroupState.PENDING_ADD) {
+                    existing.setState(Group.GroupState.ADDED);
+                    event = new GroupEvent(GroupEvent.Type.GROUP_ADDED, existing);
+                } else {
+                    if (existing.state() == Group.GroupState.PENDING_UPDATE) {
+                        existing.setState(Group.GroupState.ADDED);
+                    }
+                    event = new GroupEvent(GroupEvent.Type.GROUP_UPDATED, existing);
+                }
+            }
+        }
+
+        if (event != null) {
+            notifyDelegate(networkId, event);
+        }
+    }
+
+    @Override
+    public void removeGroupEntry(NetworkId networkId, Group group) {
+        StoredGroupEntry existing = null;
+        if (groupEntriesById.get(networkId) != null
+                && groupEntriesById.get(networkId).get(group.deviceId()) != null) {
+           existing = groupEntriesById
+                   .get(networkId).get(group.deviceId()).get(group.id());
+        }
+
+        if (existing != null) {
+            ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                    getGroupKeyTable(networkId, existing.deviceId());
+            ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                    getGroupIdTable(networkId, existing.deviceId());
+            idTable.remove(existing.id());
+            keyTable.remove(existing.appCookie());
+            notifyDelegate(networkId,
+                           new GroupEvent(GroupEvent.Type.GROUP_REMOVED, existing));
+        }
+    }
+
+    @Override
+    public void purgeGroupEntry(NetworkId networkId, DeviceId deviceId) {
+        if (groupEntriesById.get(networkId) != null) {
+            Set<Map.Entry<GroupId, StoredGroupEntry>> entryPendingRemove =
+                    groupEntriesById.get(networkId).get(deviceId).entrySet();
+            groupEntriesById.get(networkId).remove(deviceId);
+            groupEntriesByKey.get(networkId).remove(deviceId);
+
+            entryPendingRemove.forEach(entry -> {
+                notifyDelegate(networkId,
+                               new GroupEvent(GroupEvent.Type.GROUP_REMOVED,
+                                              entry.getValue()));
+            });
+        }
+    }
+
+    @Override
+    public void purgeGroupEntries(NetworkId networkId) {
+        if (groupEntriesById.get(networkId) != null) {
+            groupEntriesById.get((networkId)).values().forEach(groupEntries -> {
+                groupEntries.entrySet().forEach(entry -> {
+                    notifyDelegate(networkId,
+                                   new GroupEvent(GroupEvent.Type.GROUP_REMOVED,
+                                                  entry.getValue()));
+                });
+            });
+
+            groupEntriesById.get(networkId).clear();
+            groupEntriesByKey.get(networkId).clear();
+        }
+    }
+
+    @Override
+    public void addOrUpdateExtraneousGroupEntry(NetworkId networkId, Group group) {
+        ConcurrentMap<GroupId, Group> extraneousIdTable =
+                getExtraneousGroupIdTable(networkId, group.deviceId());
+        extraneousIdTable.put(group.id(), group);
+        // Check the reference counter
+        if (group.referenceCount() == 0) {
+            notifyDelegate(networkId,
+                           new GroupEvent(GroupEvent.Type.GROUP_REMOVE_REQUESTED, group));
+        }
+    }
+
+    @Override
+    public void removeExtraneousGroupEntry(NetworkId networkId, Group group) {
+        ConcurrentMap<GroupId, Group> extraneousIdTable =
+                getExtraneousGroupIdTable(networkId, group.deviceId());
+        extraneousIdTable.remove(group.id());
+    }
+
+    @Override
+    public Iterable<Group> getExtraneousGroups(NetworkId networkId, DeviceId deviceId) {
+        // flatten and make iterator unmodifiable
+        return FluentIterable.from(
+                getExtraneousGroupIdTable(networkId, deviceId).values());
+    }
+
+    @Override
+    public void deviceInitialAuditCompleted(NetworkId networkId, DeviceId deviceId,
+                                            boolean completed) {
+        deviceAuditStatus.computeIfAbsent(networkId, k -> new HashMap<>());
+
+        HashMap<DeviceId, Boolean> deviceAuditStatusByNetwork =
+                deviceAuditStatus.get(networkId);
+
+        synchronized (deviceAuditStatusByNetwork) {
+            if (completed) {
+                log.debug("deviceInitialAuditCompleted: AUDIT "
+                                  + "completed for device {}", deviceId);
+                deviceAuditStatusByNetwork.put(deviceId, true);
+                // Execute all pending group requests
+                ConcurrentMap<GroupKey, StoredGroupEntry> pendingGroupRequests =
+                        getPendingGroupKeyTable(networkId, deviceId);
+                for (Group group:pendingGroupRequests.values()) {
+                    GroupDescription tmp = new DefaultGroupDescription(
+                            group.deviceId(),
+                            group.type(),
+                            group.buckets(),
+                            group.appCookie(),
+                            group.givenGroupId(),
+                            group.appId());
+                    storeGroupDescriptionInternal(networkId, tmp);
+                }
+                getPendingGroupKeyTable(networkId, deviceId).clear();
+            } else {
+                if (deviceAuditStatusByNetwork.get(deviceId)) {
+                    log.debug("deviceInitialAuditCompleted: Clearing AUDIT "
+                                      + "status for device {}", deviceId);
+                    deviceAuditStatusByNetwork.put(deviceId, false);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean deviceInitialAuditStatus(NetworkId networkId, DeviceId deviceId) {
+        deviceAuditStatus.computeIfAbsent(networkId, k -> new HashMap<>());
+
+        HashMap<DeviceId, Boolean> deviceAuditStatusByNetwork =
+                deviceAuditStatus.get(networkId);
+
+        synchronized (deviceAuditStatusByNetwork) {
+            return (deviceAuditStatusByNetwork.get(deviceId) != null)
+                    ? deviceAuditStatusByNetwork.get(deviceId) : false;
+        }
+    }
+
+    @Override
+    public void groupOperationFailed(NetworkId networkId, DeviceId deviceId,
+                                     GroupOperation operation) {
+
+        StoredGroupEntry existing = null;
+        if (groupEntriesById.get(networkId) != null &&
+                groupEntriesById.get(networkId).get(deviceId) != null) {
+            existing = groupEntriesById.get(networkId).get(deviceId)
+                    .get(operation.groupId());
+        }
+
+        if (existing == null) {
+            log.warn("No group entry with ID {} found ", operation.groupId());
+            return;
+        }
+
+        switch (operation.opType()) {
+            case ADD:
+                notifyDelegate(networkId,
+                               new GroupEvent(GroupEvent.Type.GROUP_ADD_FAILED,
+                                              existing));
+                break;
+            case MODIFY:
+                notifyDelegate(networkId,
+                               new GroupEvent(GroupEvent.Type.GROUP_UPDATE_FAILED,
+                                              existing));
+                break;
+            case DELETE:
+                notifyDelegate(networkId,
+                               new GroupEvent(GroupEvent.Type.GROUP_REMOVE_FAILED,
+                                              existing));
+                break;
+            default:
+                log.warn("Unknown group operation type {}", operation.opType());
+        }
+
+        ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                getGroupKeyTable(networkId, existing.deviceId());
+        ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                getGroupIdTable(networkId, existing.deviceId());
+        idTable.remove(existing.id());
+        keyTable.remove(existing.appCookie());
+    }
+
+    @Override
+    public void pushGroupMetrics(NetworkId networkId, DeviceId deviceId,
+                                 Collection<Group> groupEntries) {
+        boolean deviceInitialAuditStatus =
+                deviceInitialAuditStatus(networkId, deviceId);
+        Set<Group> southboundGroupEntries =
+                Sets.newHashSet(groupEntries);
+        Set<Group> storedGroupEntries =
+                Sets.newHashSet(getGroups(networkId, deviceId));
+        Set<Group> extraneousStoredEntries =
+                Sets.newHashSet(getExtraneousGroups(networkId, deviceId));
+
+        if (log.isTraceEnabled()) {
+            log.trace("pushGroupMetrics: Displaying all ({}) "
+                              + "southboundGroupEntries for device {}",
+                      southboundGroupEntries.size(),
+                      deviceId);
+            for (Group group : southboundGroupEntries) {
+                log.trace("Group {} in device {}", group, deviceId);
+            }
+
+            log.trace("Displaying all ({}) stored group entries for device {}",
+                      storedGroupEntries.size(),
+                      deviceId);
+            for (Group group : storedGroupEntries) {
+                log.trace("Stored Group {} for device {}", group, deviceId);
+            }
+        }
+
+        for (Iterator<Group> it2 = southboundGroupEntries.iterator(); it2.hasNext();) {
+            Group group = it2.next();
+            if (storedGroupEntries.remove(group)) {
+                // we both have the group, let's update some info then.
+                log.trace("Group AUDIT: group {} exists "
+                                  + "in both planes for device {}",
+                          group.id(), deviceId);
+                groupAdded(networkId, group);
+                it2.remove();
+            }
+        }
+        for (Group group : southboundGroupEntries) {
+            if (getGroup(networkId, group.deviceId(), group.id()) != null) {
+                // There is a group existing with the same id
+                // It is possible that group update is
+                // in progress while we got a stale info from switch
+                if (!storedGroupEntries.remove(getGroup(
+                        networkId, group.deviceId(), group.id()))) {
+                    log.warn("Group AUDIT: Inconsistent state:"
+                                     + "Group exists in ID based table while "
+                                     + "not present in key based table");
+                }
+            } else {
+                // there are groups in the switch that aren't in the store
+                log.trace("Group AUDIT: extraneous group {} exists "
+                                  + "in data plane for device {}",
+                          group.id(), deviceId);
+                extraneousStoredEntries.remove(group);
+                extraneousGroup(networkId, group);
+            }
+        }
+        for (Group group : storedGroupEntries) {
+            // there are groups in the store that aren't in the switch
+            log.trace("Group AUDIT: group {} missing "
+                              + "in data plane for device {}",
+                      group.id(), deviceId);
+            groupMissing(networkId, group);
+        }
+        for (Group group : extraneousStoredEntries) {
+            // there are groups in the extraneous store that
+            // aren't in the switch
+            log.trace("Group AUDIT: clearing extransoeus group {} "
+                              + "from store for device {}",
+                      group.id(), deviceId);
+            removeExtraneousGroupEntry(networkId, group);
+        }
+
+        if (!deviceInitialAuditStatus) {
+            log.debug("Group AUDIT: Setting device {} initial "
+                              + "AUDIT completed", deviceId);
+            deviceInitialAuditCompleted(networkId, deviceId, true);
+        }
+    }
+
+    @Override
+    public void notifyOfFailovers(NetworkId networkId, Collection<Group> failoverGroups) {
+        List<GroupEvent> failoverEvents = new ArrayList<>();
+        failoverGroups.forEach(group -> {
+            if (group.type() == Group.Type.FAILOVER) {
+                failoverEvents.add(new GroupEvent(GroupEvent.Type.GROUP_BUCKET_FAILOVER, group));
+            }
+        });
+        notifyDelegate(networkId, failoverEvents);
+    }
+
+    private void groupMissing(NetworkId networkId, Group group) {
+        switch (group.state()) {
+            case PENDING_DELETE:
+                log.debug("Group {} delete confirmation from device {} " +
+                                  "of virtaual network {}",
+                          group, group.deviceId(), networkId);
+                removeGroupEntry(networkId, group);
+                break;
+            case ADDED:
+            case PENDING_ADD:
+            case PENDING_UPDATE:
+                log.debug("Group {} is in store but not on device {}",
+                          group, group.deviceId());
+                StoredGroupEntry existing = null;
+                if (groupEntriesById.get(networkId) != null &&
+                        groupEntriesById.get(networkId).get(group.deviceId()) != null) {
+
+                    existing = groupEntriesById.get(networkId)
+                            .get(group.deviceId()).get(group.id());
+                }
+                if (existing == null) {
+                    break;
+                }
+
+                log.trace("groupMissing: group "
+                                  + "entry {} in device {} moving "
+                                  + "from {} to PENDING_ADD",
+                          existing.id(),
+                          existing.deviceId(),
+                          existing.state());
+                existing.setState(Group.GroupState.PENDING_ADD);
+                notifyDelegate(networkId, new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+                                              group));
+                break;
+            default:
+                log.debug("Virtual network {} : Group {} has not been installed.",
+                          networkId, group);
+                break;
+        }
+    }
+
+    private void extraneousGroup(NetworkId networkId, Group group) {
+        log.debug("Group {} is on device {} of virtual network{}, but not in store.",
+                  group, group.deviceId(), networkId);
+        addOrUpdateExtraneousGroupEntry(networkId, group);
+    }
+
+    private void groupAdded(NetworkId networkId, Group group) {
+        log.trace("Group {} Added or Updated in device {} of virtual network {}",
+                  group, group.deviceId(), networkId);
+        addOrUpdateGroupEntry(networkId, group);
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualIntentStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualIntentStore.java
new file mode 100644
index 0000000..15b6bcd
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualIntentStore.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentData;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.IntentStoreDelegate;
+import org.onosproject.net.intent.Key;
+import org.onosproject.store.Timestamp;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.PURGE_REQ;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple single-instance implementation of the intent store for virtual networks.
+ */
+
+@Component(immediate = true, service = VirtualNetworkIntentStore.class)
+public class SimpleVirtualIntentStore
+        extends AbstractVirtualStore<IntentEvent, IntentStoreDelegate>
+        implements VirtualNetworkIntentStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private final Map<NetworkId, Map<Key, IntentData>> currentByNetwork =
+            Maps.newConcurrentMap();
+    private final Map<NetworkId, Map<Key, IntentData>> pendingByNetwork =
+            Maps.newConcurrentMap();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+
+    @Override
+    public long getIntentCount(NetworkId networkId) {
+        return getCurrentMap(networkId).size();
+    }
+
+    @Override
+    public Iterable<Intent> getIntents(NetworkId networkId) {
+        return getCurrentMap(networkId).values().stream()
+                .map(IntentData::intent)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Iterable<IntentData> getIntentData(NetworkId networkId,
+                                              boolean localOnly, long olderThan) {
+        if (localOnly || olderThan > 0) {
+            long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
+            final SystemClockTimestamp time = new SystemClockTimestamp(older);
+            return getCurrentMap(networkId).values().stream()
+                    .filter(data -> data.version().isOlderThan(time) &&
+                            (!localOnly || isMaster(networkId, data.key())))
+                    .collect(Collectors.toList());
+        }
+        return Lists.newArrayList(getCurrentMap(networkId).values());
+    }
+
+    @Override
+    public IntentState getIntentState(NetworkId networkId, Key intentKey) {
+        IntentData data = getCurrentMap(networkId).get(intentKey);
+        return (data != null) ? data.state() : null;
+    }
+
+    @Override
+    public List<Intent> getInstallableIntents(NetworkId networkId, Key intentKey) {
+        IntentData data = getCurrentMap(networkId).get(intentKey);
+        if (data != null) {
+            return data.installables();
+        }
+        return null;
+    }
+
+    @Override
+    public void write(NetworkId networkId, IntentData newData) {
+        checkNotNull(newData);
+
+        synchronized (this) {
+            // TODO this could be refactored/cleaned up
+            IntentData currentData = getCurrentMap(networkId).get(newData.key());
+            IntentData pendingData = getPendingMap(networkId).get(newData.key());
+
+            if (IntentData.isUpdateAcceptable(currentData, newData)) {
+                if (pendingData != null) {
+                    if (pendingData.state() == PURGE_REQ) {
+                        getCurrentMap(networkId).remove(newData.key(), newData);
+                    } else {
+                        getCurrentMap(networkId).put(newData.key(), IntentData.copy(newData));
+                    }
+
+                    if (pendingData.version().compareTo(newData.version()) <= 0) {
+                        // pendingData version is less than or equal to newData's
+                        // Note: a new update for this key could be pending (it's version will be greater)
+                        getPendingMap(networkId).remove(newData.key());
+                    }
+                }
+                IntentEvent.getEvent(newData).ifPresent(e -> notifyDelegate(networkId, e));
+            }
+        }
+    }
+
+    @Override
+    public void batchWrite(NetworkId networkId, Iterable<IntentData> updates) {
+        for (IntentData data : updates) {
+            write(networkId, data);
+        }
+    }
+
+    @Override
+    public Intent getIntent(NetworkId networkId, Key key) {
+        IntentData data = getCurrentMap(networkId).get(key);
+        return (data != null) ? data.intent() : null;
+    }
+
+    @Override
+    public IntentData getIntentData(NetworkId networkId, Key key) {
+        IntentData currentData = getCurrentMap(networkId).get(key);
+        if (currentData == null) {
+            return null;
+        }
+        return IntentData.copy(currentData);
+    }
+
+    @Override
+    public void addPending(NetworkId networkId, IntentData data) {
+        if (data.version() == null) { // recompiled intents will already have a version
+            data = new IntentData(data.intent(), data.state(), new SystemClockTimestamp());
+        }
+        synchronized (this) {
+            IntentData existingData = getPendingMap(networkId).get(data.key());
+            if (existingData == null ||
+                    // existing version is strictly less than data's version
+                    // Note: if they are equal, we already have the update
+                    // TODO maybe we should still make this <= to be safe?
+                    existingData.version().compareTo(data.version()) < 0) {
+                getPendingMap(networkId).put(data.key(), data);
+
+                checkNotNull(delegateMap.get(networkId), "Store delegate is not set")
+                        .process(IntentData.copy(data));
+                IntentEvent.getEvent(data).ifPresent(e -> notifyDelegate(networkId, e));
+            } else {
+                log.debug("IntentData {} is older than existing: {}",
+                          data, existingData);
+            }
+            //TODO consider also checking the current map at this point
+        }
+    }
+
+    @Override
+    public boolean isMaster(NetworkId networkId, Key intentKey) {
+        return true;
+    }
+
+    @Override
+    public Iterable<Intent> getPending(NetworkId networkId) {
+        return getPendingMap(networkId).values().stream()
+                .map(IntentData::intent)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Iterable<IntentData> getPendingData(NetworkId networkId) {
+        return Lists.newArrayList(getPendingMap(networkId).values());
+    }
+
+    @Override
+    public IntentData getPendingData(NetworkId networkId, Key intentKey) {
+        return getPendingMap(networkId).get(intentKey);
+    }
+
+    @Override
+    public Iterable<IntentData> getPendingData(NetworkId networkId,
+                                               boolean localOnly, long olderThan) {
+        long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
+        final SystemClockTimestamp time = new SystemClockTimestamp(older);
+        return getPendingMap(networkId).values().stream()
+                .filter(data -> data.version().isOlderThan(time) &&
+                        (!localOnly || isMaster(networkId, data.key())))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns the current intent map for a specific virtual network.
+     *
+     * @param networkId a virtual network identifier
+     * @return the current map for the requested virtual network
+     */
+    private Map<Key, IntentData> getCurrentMap(NetworkId networkId) {
+        currentByNetwork.computeIfAbsent(networkId,
+                                   n -> Maps.newConcurrentMap());
+        return currentByNetwork.get(networkId);
+    }
+
+    /**
+     * Returns the pending intent map for a specific virtual network.
+     *
+     * @param networkId a virtual network identifier
+     * @return the pending intent map for the requested virtual network
+     */
+    private Map<Key, IntentData> getPendingMap(NetworkId networkId) {
+        pendingByNetwork.computeIfAbsent(networkId,
+                                   n -> Maps.newConcurrentMap());
+        return pendingByNetwork.get(networkId);
+    }
+
+    public class SystemClockTimestamp implements Timestamp {
+
+        private final long nanoTimestamp;
+
+        public SystemClockTimestamp() {
+            nanoTimestamp = System.nanoTime();
+        }
+
+        public SystemClockTimestamp(long timestamp) {
+            nanoTimestamp = timestamp;
+        }
+
+        @Override
+        public int compareTo(Timestamp o) {
+            checkArgument(o instanceof SystemClockTimestamp,
+                          "Must be SystemClockTimestamp", o);
+            SystemClockTimestamp that = (SystemClockTimestamp) o;
+
+            return ComparisonChain.start()
+                    .compare(this.nanoTimestamp, that.nanoTimestamp)
+                    .result();
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStore.java
new file mode 100644
index 0000000..68d8ed8
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStore.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterEventListener;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.Node;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipStoreDelegate;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual network mastership store to manage inventory of
+ * mastership using trivial in-memory implementation.
+ */
+@Component(immediate = true, service = VirtualNetworkMastershipStore.class)
+public class SimpleVirtualMastershipStore
+        extends AbstractVirtualStore<MastershipEvent, MastershipStoreDelegate>
+        implements VirtualNetworkMastershipStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final int NOTHING = 0;
+    private static final int INIT = 1;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected VersionService versionService;
+
+    //devices mapped to their masters, to emulate multiple nodes
+    protected final Map<NetworkId, Map<DeviceId, NodeId>> masterMapByNetwork =
+            new HashMap<>();
+    //emulate backups with pile of nodes
+    protected final Map<NetworkId, Map<DeviceId, List<NodeId>>> backupsByNetwork =
+            new HashMap<>();
+    //terms
+    protected final Map<NetworkId, Map<DeviceId, AtomicInteger>> termMapByNetwork =
+            new HashMap<>();
+
+    @Activate
+    public void activate() {
+        if (clusterService == null) {
+            clusterService = createFakeClusterService();
+        }
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public CompletableFuture<MastershipRole> requestRole(NetworkId networkId,
+                                                         DeviceId deviceId) {
+        //query+possible reelection
+        NodeId node = clusterService.getLocalNode().id();
+        MastershipRole role = getRole(networkId, node, deviceId);
+
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+        switch (role) {
+            case MASTER:
+                return CompletableFuture.completedFuture(MastershipRole.MASTER);
+            case STANDBY:
+                if (getMaster(networkId, deviceId) == null) {
+                    // no master => become master
+                    masterMap.put(deviceId, node);
+                    incrementTerm(networkId, deviceId);
+                    // remove from backup list
+                    removeFromBackups(networkId, deviceId, node);
+                    notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
+                        getMastership(networkId, deviceId)));
+                    return CompletableFuture.completedFuture(MastershipRole.MASTER);
+                }
+                return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+            case NONE:
+                if (getMaster(networkId, deviceId) == null) {
+                    // no master => become master
+                    masterMap.put(deviceId, node);
+                    incrementTerm(networkId, deviceId);
+                    notifyDelegate(networkId, new MastershipEvent(MASTER_CHANGED, deviceId,
+                        getMastership(networkId, deviceId)));
+                    return CompletableFuture.completedFuture(MastershipRole.MASTER);
+                }
+                // add to backup list
+                if (addToBackup(networkId, deviceId, node)) {
+                    notifyDelegate(networkId, new MastershipEvent(BACKUPS_CHANGED, deviceId,
+                        getMastership(networkId, deviceId)));
+                }
+                return CompletableFuture.completedFuture(MastershipRole.STANDBY);
+            default:
+                log.warn("unknown Mastership Role {}", role);
+        }
+        return CompletableFuture.completedFuture(role);
+    }
+
+    @Override
+    public MastershipRole getRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+        //just query
+        NodeId current = masterMap.get(deviceId);
+        MastershipRole role;
+
+        if (current != null && current.equals(nodeId)) {
+            return MastershipRole.MASTER;
+        }
+
+        if (backups.getOrDefault(deviceId, Collections.emptyList()).contains(nodeId)) {
+            role = MastershipRole.STANDBY;
+        } else {
+            role = MastershipRole.NONE;
+        }
+        return role;
+    }
+
+    @Override
+    public NodeId getMaster(NetworkId networkId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+        return masterMap.get(deviceId);
+    }
+
+    @Override
+    public RoleInfo getNodes(NetworkId networkId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+        return new RoleInfo(masterMap.get(deviceId),
+                            backups.getOrDefault(deviceId, ImmutableList.of()));
+    }
+
+    @Override
+    public MastershipInfo getMastership(NetworkId networkId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+        Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+        ImmutableMap.Builder<NodeId, MastershipRole> roleBuilder = ImmutableMap.builder();
+        NodeId master = masterMap.get(deviceId);
+        if (master != null) {
+            roleBuilder.put(master, MastershipRole.MASTER);
+        }
+        backups.getOrDefault(deviceId, Collections.emptyList())
+            .forEach(nodeId -> roleBuilder.put(nodeId, MastershipRole.STANDBY));
+        clusterService.getNodes().stream()
+            .filter(node -> !masterMap.containsValue(node.id()))
+            .filter(node -> !backups.get(deviceId).contains(node.id()))
+            .forEach(node -> roleBuilder.put(node.id(), MastershipRole.NONE));
+        return new MastershipInfo(
+            termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING)).get(),
+            Optional.ofNullable(master),
+            roleBuilder.build());
+    }
+
+    @Override
+    public Set<DeviceId> getDevices(NetworkId networkId, NodeId nodeId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+        Set<DeviceId> ids = new HashSet<>();
+        for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
+            if (Objects.equals(d.getValue(), nodeId)) {
+                ids.add(d.getKey());
+            }
+        }
+        return ids;
+    }
+
+    @Override
+    public synchronized CompletableFuture<MastershipEvent> setMaster(NetworkId networkId,
+                                                        NodeId nodeId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+        MastershipRole role = getRole(networkId, nodeId, deviceId);
+        switch (role) {
+            case MASTER:
+                // no-op
+                return CompletableFuture.completedFuture(null);
+            case STANDBY:
+            case NONE:
+                NodeId prevMaster = masterMap.put(deviceId, nodeId);
+                incrementTerm(networkId, deviceId);
+                removeFromBackups(networkId, deviceId, nodeId);
+                addToBackup(networkId, deviceId, prevMaster);
+                break;
+            default:
+                log.warn("unknown Mastership Role {}", role);
+                return null;
+        }
+
+        return CompletableFuture.completedFuture(
+                new MastershipEvent(MASTER_CHANGED, deviceId, getMastership(networkId, deviceId)));
+    }
+
+    @Override
+    public MastershipTerm getTermFor(NetworkId networkId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+        Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
+
+        if ((termMap.get(deviceId) == null)) {
+            return MastershipTerm.of(masterMap.get(deviceId), NOTHING);
+        }
+        return MastershipTerm.of(
+                masterMap.get(deviceId), termMap.get(deviceId).get());
+    }
+
+    @Override
+    public CompletableFuture<MastershipEvent> setStandby(NetworkId networkId,
+                                                         NodeId nodeId, DeviceId deviceId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+        MastershipRole role = getRole(networkId, nodeId, deviceId);
+        switch (role) {
+            case MASTER:
+                NodeId backup = reelect(networkId, deviceId, nodeId);
+                if (backup == null) {
+                    // no master alternative
+                    masterMap.remove(deviceId);
+                    // TODO: Should there be new event type for no MASTER?
+                    return CompletableFuture.completedFuture(
+                            new MastershipEvent(MASTER_CHANGED, deviceId,
+                                getMastership(networkId, deviceId)));
+                } else {
+                    NodeId prevMaster = masterMap.put(deviceId, backup);
+                    incrementTerm(networkId, deviceId);
+                    addToBackup(networkId, deviceId, prevMaster);
+                    return CompletableFuture.completedFuture(
+                            new MastershipEvent(MASTER_CHANGED, deviceId,
+                                getMastership(networkId, deviceId)));
+                }
+
+            case STANDBY:
+            case NONE:
+                boolean modified = addToBackup(networkId, deviceId, nodeId);
+                if (modified) {
+                    return CompletableFuture.completedFuture(
+                            new MastershipEvent(BACKUPS_CHANGED, deviceId,
+                                getMastership(networkId, deviceId)));
+                }
+                break;
+
+            default:
+                log.warn("unknown Mastership Role {}", role);
+        }
+        return null;
+    }
+
+
+    /**
+     * Dumbly selects next-available node that's not the current one.
+     * emulate leader election.
+     *
+     * @param networkId a virtual network identifier
+     * @param deviceId a virtual device identifier
+     * @param nodeId a nod identifier
+     * @return Next available node as a leader
+     */
+    private synchronized NodeId reelect(NetworkId networkId, DeviceId deviceId,
+                                        NodeId nodeId) {
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+        List<NodeId> stbys = backups.getOrDefault(deviceId, Collections.emptyList());
+        NodeId backup = null;
+        for (NodeId n : stbys) {
+            if (!n.equals(nodeId)) {
+                backup = n;
+                break;
+            }
+        }
+        stbys.remove(backup);
+        return backup;
+    }
+
+    @Override
+    public synchronized CompletableFuture<MastershipEvent>
+    relinquishRole(NetworkId networkId, NodeId nodeId, DeviceId deviceId) {
+    Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+
+        MastershipRole role = getRole(networkId, nodeId, deviceId);
+        switch (role) {
+            case MASTER:
+                NodeId backup = reelect(networkId, deviceId, nodeId);
+                masterMap.put(deviceId, backup);
+                incrementTerm(networkId, deviceId);
+                return CompletableFuture.completedFuture(
+                        new MastershipEvent(MASTER_CHANGED, deviceId,
+                            getMastership(networkId, deviceId)));
+
+            case STANDBY:
+                if (removeFromBackups(networkId, deviceId, nodeId)) {
+                    return CompletableFuture.completedFuture(
+                            new MastershipEvent(BACKUPS_CHANGED, deviceId,
+                                getMastership(networkId, deviceId)));
+                }
+                break;
+
+            case NONE:
+                break;
+
+            default:
+                log.warn("unknown Mastership Role {}", role);
+        }
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public void relinquishAllRole(NetworkId networkId, NodeId nodeId) {
+        Map<DeviceId, NodeId> masterMap = getMasterMap(networkId);
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+        List<CompletableFuture<MastershipEvent>> eventFutures = new ArrayList<>();
+        Set<DeviceId> toRelinquish = new HashSet<>();
+
+        masterMap.entrySet().stream()
+                .filter(entry -> nodeId.equals(entry.getValue()))
+                .forEach(entry -> toRelinquish.add(entry.getKey()));
+
+        backups.entrySet().stream()
+                .filter(entry -> entry.getValue().contains(nodeId))
+                .forEach(entry -> toRelinquish.add(entry.getKey()));
+
+        toRelinquish.forEach(deviceId -> eventFutures.add(
+                relinquishRole(networkId, nodeId, deviceId)));
+
+        eventFutures.forEach(future -> {
+            future.whenComplete((event, error) -> notifyDelegate(networkId, event));
+        });
+    }
+
+    /**
+     * Increase the term for a device, and store it.
+     *
+     * @param networkId a virtual network identifier
+     * @param deviceId a virtual device identifier
+     */
+    private synchronized void incrementTerm(NetworkId networkId, DeviceId deviceId) {
+        Map<DeviceId, AtomicInteger> termMap = getTermMap(networkId);
+
+        AtomicInteger term = termMap.getOrDefault(deviceId, new AtomicInteger(NOTHING));
+        term.incrementAndGet();
+        termMap.put(deviceId, term);
+    }
+
+    /**
+     * Remove backup node for a device.
+     *
+     * @param networkId a virtual network identifier
+     * @param deviceId a virtual device identifier
+     * @param nodeId a node identifier
+     * @return True if success
+     */
+    private synchronized boolean removeFromBackups(NetworkId networkId,
+                                                   DeviceId deviceId, NodeId nodeId) {
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+        List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+        boolean modified = stbys.remove(nodeId);
+        backups.put(deviceId, stbys);
+        return modified;
+    }
+
+    /**
+     * add to backup if not there already, silently ignores null node.
+     *
+     * @param networkId a virtual network identifier
+     * @param deviceId a virtual device identifier
+     * @param nodeId a node identifier
+     * @return True if success
+     */
+    private synchronized boolean addToBackup(NetworkId networkId,
+                                             DeviceId deviceId, NodeId nodeId) {
+        Map<DeviceId, List<NodeId>> backups = getBackups(networkId);
+
+        boolean modified = false;
+        List<NodeId> stbys = backups.getOrDefault(deviceId, new ArrayList<>());
+        if (nodeId != null && !stbys.contains(nodeId)) {
+            stbys.add(nodeId);
+            backups.put(deviceId, stbys);
+            modified = true;
+        }
+        return modified;
+    }
+
+    /**
+     * Returns deviceId-master map for a specified virtual network.
+     *
+     * @param networkId a virtual network identifier
+     * @return DeviceId-master map of a given virtual network.
+     */
+    private Map<DeviceId, NodeId> getMasterMap(NetworkId networkId) {
+        return masterMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
+    }
+
+    /**
+     * Returns deviceId-backups map for a specified virtual network.
+     *
+     * @param networkId a virtual network identifier
+     * @return DeviceId-backups map of a given virtual network.
+     */
+    private Map<DeviceId, List<NodeId>> getBackups(NetworkId networkId) {
+        return backupsByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
+    }
+
+    /**
+     * Returns deviceId-terms map for a specified virtual network.
+     *
+     * @param networkId a virtual network identifier
+     * @return DeviceId-terms map of a given virtual network.
+     */
+    private Map<DeviceId, AtomicInteger> getTermMap(NetworkId networkId) {
+        return termMapByNetwork.computeIfAbsent(networkId, k -> new HashMap<>());
+    }
+
+    /**
+     * Returns a fake cluster service for a test purpose only.
+     *
+     * @return a fake cluster service
+     */
+    private ClusterService createFakeClusterService() {
+        // just for ease of unit test
+        final ControllerNode instance =
+                new DefaultControllerNode(new NodeId("local"),
+                                          IpAddress.valueOf("127.0.0.1"));
+
+        ClusterService faceClusterService = new ClusterService() {
+
+            private final Instant creationTime = Instant.now();
+
+            @Override
+            public ControllerNode getLocalNode() {
+                return instance;
+            }
+
+            @Override
+            public Set<ControllerNode> getNodes() {
+                return ImmutableSet.of(instance);
+            }
+
+            @Override
+            public Set<Node> getConsensusNodes() {
+                return ImmutableSet.of();
+            }
+
+            @Override
+            public ControllerNode getNode(NodeId nodeId) {
+                if (instance.id().equals(nodeId)) {
+                    return instance;
+                }
+                return null;
+            }
+
+            @Override
+            public ControllerNode.State getState(NodeId nodeId) {
+                if (instance.id().equals(nodeId)) {
+                    return ControllerNode.State.ACTIVE;
+                } else {
+                    return ControllerNode.State.INACTIVE;
+                }
+            }
+
+            @Override
+            public Version getVersion(NodeId nodeId) {
+                if (instance.id().equals(nodeId)) {
+                    return versionService.version();
+                }
+                return null;
+            }
+
+            @Override
+            public Instant getLastUpdatedInstant(NodeId nodeId) {
+                return creationTime;
+            }
+
+            @Override
+            public void addListener(ClusterEventListener listener) {
+            }
+
+            @Override
+            public void removeListener(ClusterEventListener listener) {
+            }
+        };
+        return faceClusterService;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMeterStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMeterStore.java
new file mode 100644
index 0000000..5add89d
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMeterStore.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Maps;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMeterStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterFeatures;
+import org.onosproject.net.meter.MeterFeaturesKey;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterStoreDelegate;
+import org.onosproject.net.meter.MeterStoreResult;
+import org.onosproject.store.service.StorageException;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.onosproject.net.meter.MeterFailReason.TIMEOUT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the virtual meter store for a single instance.
+ */
+//TODO: support distributed meter store for virtual networks
+@Component(immediate = true, service = VirtualNetworkMeterStore.class)
+public class SimpleVirtualMeterStore
+        extends AbstractVirtualStore<MeterEvent, MeterStoreDelegate>
+        implements VirtualNetworkMeterStore {
+
+        private Logger log = getLogger(getClass());
+
+        @Reference(cardinality = ReferenceCardinality.MANDATORY)
+        protected ClusterService clusterService;
+
+        private ConcurrentMap<NetworkId, ConcurrentMap<MeterKey, MeterData>> meterMap =
+                Maps.newConcurrentMap();
+
+        private NodeId local;
+
+        private ConcurrentMap<NetworkId, ConcurrentMap<MeterFeaturesKey, MeterFeatures>>
+                meterFeatureMap = Maps.newConcurrentMap();
+
+        private ConcurrentMap<NetworkId,
+                ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>>> futuresMap =
+                Maps.newConcurrentMap();
+
+        @Activate
+        public void activate() {
+            log.info("Started");
+            local = clusterService.getLocalNode().id();
+        }
+
+        @Deactivate
+        public void deactivate() {
+            log.info("Stopped");
+        }
+
+        private ConcurrentMap<MeterKey, MeterData> getMetersByNetwork(NetworkId networkId) {
+            meterMap.computeIfAbsent(networkId, m -> new ConcurrentHashMap<>());
+            return meterMap.get(networkId);
+        }
+
+        private ConcurrentMap<MeterFeaturesKey, MeterFeatures>
+        getMeterFeaturesByNetwork(NetworkId networkId) {
+            meterFeatureMap.computeIfAbsent(networkId, f -> new ConcurrentHashMap<>());
+            return meterFeatureMap.get(networkId);
+        }
+
+        private ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>>
+        getFuturesByNetwork(NetworkId networkId) {
+            futuresMap.computeIfAbsent(networkId, f -> new ConcurrentHashMap<>());
+            return futuresMap.get(networkId);
+        }
+
+        @Override
+        public CompletableFuture<MeterStoreResult> storeMeter(NetworkId networkId, Meter meter) {
+
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+            ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+                   getFuturesByNetwork(networkId);
+
+            CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
+            MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+            futures.put(key, future);
+            MeterData data = new MeterData(meter, null, local);
+
+            try {
+                    meters.put(key, data);
+            } catch (StorageException e) {
+                    future.completeExceptionally(e);
+            }
+
+            return future;
+        }
+
+        @Override
+        public CompletableFuture<MeterStoreResult> deleteMeter(NetworkId networkId, Meter meter) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+            ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+                    getFuturesByNetwork(networkId);
+
+            CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
+            MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+            futures.put(key, future);
+
+            MeterData data = new MeterData(meter, null, local);
+
+            // update the state of the meter. It will be pruned by observing
+            // that it has been removed from the dataplane.
+            try {
+                    if (meters.computeIfPresent(key, (k, v) -> data) == null) {
+                            future.complete(MeterStoreResult.success());
+                    }
+            } catch (StorageException e) {
+                    future.completeExceptionally(e);
+            }
+
+            return future;
+        }
+
+        @Override
+        public MeterStoreResult storeMeterFeatures(NetworkId networkId, MeterFeatures meterfeatures) {
+            ConcurrentMap<MeterFeaturesKey, MeterFeatures> meterFeatures
+                    = getMeterFeaturesByNetwork(networkId);
+
+            MeterStoreResult result = MeterStoreResult.success();
+            MeterFeaturesKey key = MeterFeaturesKey.key(meterfeatures.deviceId());
+            try {
+                    meterFeatures.putIfAbsent(key, meterfeatures);
+            } catch (StorageException e) {
+                    result = MeterStoreResult.fail(TIMEOUT);
+            }
+            return result;
+        }
+
+        @Override
+        public MeterStoreResult deleteMeterFeatures(NetworkId networkId, DeviceId deviceId) {
+            ConcurrentMap<MeterFeaturesKey, MeterFeatures> meterFeatures
+                    = getMeterFeaturesByNetwork(networkId);
+
+            MeterStoreResult result = MeterStoreResult.success();
+            MeterFeaturesKey key = MeterFeaturesKey.key(deviceId);
+            try {
+                    meterFeatures.remove(key);
+            } catch (StorageException e) {
+                    result = MeterStoreResult.fail(TIMEOUT);
+            }
+            return result;
+        }
+
+        @Override
+        public CompletableFuture<MeterStoreResult> updateMeter(NetworkId networkId, Meter meter) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+            ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+                    getFuturesByNetwork(networkId);
+
+            CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
+            MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+            futures.put(key, future);
+
+            MeterData data = new MeterData(meter, null, local);
+            try {
+                    if (meters.computeIfPresent(key, (k, v) -> data) == null) {
+                            future.complete(MeterStoreResult.fail(MeterFailReason.INVALID_METER));
+                    }
+            } catch (StorageException e) {
+                    future.completeExceptionally(e);
+            }
+            return future;
+        }
+
+        @Override
+        public void updateMeterState(NetworkId networkId, Meter meter) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+            MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+            meters.computeIfPresent(key, (k, v) -> {
+                    DefaultMeter m = (DefaultMeter) v.meter();
+                    m.setState(meter.state());
+                    m.setProcessedPackets(meter.packetsSeen());
+                    m.setProcessedBytes(meter.bytesSeen());
+                    m.setLife(meter.life());
+                    // TODO: Prune if drops to zero.
+                    m.setReferenceCount(meter.referenceCount());
+                    return new MeterData(m, null, v.origin());
+            });
+        }
+
+        @Override
+        public Meter getMeter(NetworkId networkId, MeterKey key) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+            MeterData data = meters.get(key);
+            return data == null ? null : data.meter();
+        }
+
+        @Override
+        public Collection<Meter> getAllMeters(NetworkId networkId) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+            return Collections2.transform(meters.values(), MeterData::meter);
+        }
+
+        @Override
+        public void failedMeter(NetworkId networkId, MeterOperation op, MeterFailReason reason) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+
+            MeterKey key = MeterKey.key(op.meter().deviceId(), op.meter().id());
+            meters.computeIfPresent(key, (k, v) ->
+                    new MeterData(v.meter(), reason, v.origin()));
+        }
+
+        @Override
+        public void deleteMeterNow(NetworkId networkId, Meter m) {
+            ConcurrentMap<MeterKey, MeterData> meters = getMetersByNetwork(networkId);
+            ConcurrentMap<MeterKey, CompletableFuture<MeterStoreResult>> futures =
+                    getFuturesByNetwork(networkId);
+
+            MeterKey key = MeterKey.key(m.deviceId(), m.id());
+            futures.remove(key);
+            meters.remove(key);
+        }
+
+        @Override
+        public long getMaxMeters(NetworkId networkId, MeterFeaturesKey key) {
+            ConcurrentMap<MeterFeaturesKey, MeterFeatures> meterFeatures
+                    = getMeterFeaturesByNetwork(networkId);
+
+            MeterFeatures features = meterFeatures.get(key);
+            return features == null ? 0L : features.maxMeter();
+        }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualPacketStore.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualPacketStore.java
new file mode 100644
index 0000000..e0bb7c6
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualPacketStore.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketEvent;
+import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.packet.PacketStoreDelegate;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple single instance implementation of the virtual packet store.
+ */
+//TODO: support distributed packet store for virtual networks
+
+@Component(immediate = true, service = VirtualNetworkPacketStore.class)
+public class SimpleVirtualPacketStore
+        extends AbstractVirtualStore<PacketEvent, PacketStoreDelegate>
+        implements VirtualNetworkPacketStore {
+
+    private final Logger log = getLogger(getClass());
+
+    private Map<NetworkId, Map<TrafficSelector, Set<PacketRequest>>> requests
+            = Maps.newConcurrentMap();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public void emit(NetworkId networkId, OutboundPacket packet) {
+        notifyDelegate(networkId, new PacketEvent(PacketEvent.Type.EMIT, packet));
+    }
+
+    @Override
+    public void requestPackets(NetworkId networkId, PacketRequest request) {
+        requests.computeIfAbsent(networkId, k -> Maps.newConcurrentMap());
+
+        requests.get(networkId).compute(request.selector(), (s, existingRequests) -> {
+            if (existingRequests == null) {
+                if (hasDelegate(networkId)) {
+                    delegateMap.get(networkId).requestPackets(request);
+                }
+                return ImmutableSet.of(request);
+            } else if (!existingRequests.contains(request)) {
+                if (hasDelegate(networkId)) {
+                    delegateMap.get(networkId).requestPackets(request);
+                }
+                return ImmutableSet.<PacketRequest>builder()
+                        .addAll(existingRequests)
+                        .add(request)
+                        .build();
+            } else {
+                return existingRequests;
+            }
+        });
+    }
+
+    @Override
+    public void cancelPackets(NetworkId networkId, PacketRequest request) {
+        requests.get(networkId).computeIfPresent(request.selector(), (s, existingRequests) -> {
+            if (existingRequests.contains(request)) {
+                HashSet<PacketRequest> newRequests = Sets.newHashSet(existingRequests);
+                newRequests.remove(request);
+                if (hasDelegate(networkId)) {
+                    delegateMap.get(networkId).cancelPackets(request);
+                }
+                if (newRequests.size() > 0) {
+                    return ImmutableSet.copyOf(newRequests);
+                } else {
+                    return null;
+                }
+            } else {
+                return existingRequests;
+            }
+        });
+    }
+
+    @Override
+    public List<PacketRequest> existingRequests(NetworkId networkId) {
+        List<PacketRequest> list = Lists.newArrayList();
+        if (requests.get(networkId) != null) {
+            requests.get(networkId).values().forEach(list::addAll);
+            list.sort((o1, o2) -> o1.priority().priorityValue() - o2.priority().priorityValue());
+        }
+        return list;
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/package-info.java
new file mode 100644
index 0000000..99b5d82
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of virtual network stores.
+ */
+package org.onosproject.incubator.net.virtual.store.impl;
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualDeviceId.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualDeviceId.java
new file mode 100644
index 0000000..6a38661
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualDeviceId.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to isolate device id from other virtual networks.
+ */
+public class VirtualDeviceId {
+
+    NetworkId networkId;
+    DeviceId deviceId;
+
+    public VirtualDeviceId(NetworkId networkId, DeviceId deviceId) {
+        this.networkId = networkId;
+        this.deviceId = deviceId;
+    }
+
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, deviceId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof VirtualDeviceId) {
+            VirtualDeviceId that = (VirtualDeviceId) obj;
+            return this.deviceId.equals(that.deviceId) &&
+                    this.networkId.equals(that.networkId);
+        }
+        return false;
+    }
+}
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowEntry.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowEntry.java
new file mode 100644
index 0000000..2a00883
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowEntry.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.FlowEntry;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow entry.
+ */
+public class VirtualFlowEntry {
+    NetworkId networkId;
+    FlowEntry flowEntry;
+
+    public VirtualFlowEntry(NetworkId networkId, FlowEntry flowEntry) {
+        this.networkId = networkId;
+        this.flowEntry = flowEntry;
+    }
+
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    public FlowEntry flowEntry() {
+        return flowEntry;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, flowEntry);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof VirtualFlowEntry) {
+            VirtualFlowEntry that = (VirtualFlowEntry) other;
+            return this.networkId.equals(that.networkId) &&
+                    this.flowEntry.equals(that.flowEntry);
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRule.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRule.java
new file mode 100644
index 0000000..ea64904
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.FlowRule;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow rule.
+ */
+public class VirtualFlowRule {
+    NetworkId networkId;
+    FlowRule rule;
+
+    public VirtualFlowRule(NetworkId networkId, FlowRule rule) {
+        this.networkId = networkId;
+        this.rule = rule;
+    }
+
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    public FlowRule rule() {
+        return rule;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, rule);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this ==  other) {
+            return true;
+        }
+
+        if (other instanceof VirtualFlowRule) {
+            VirtualFlowRule that = (VirtualFlowRule) other;
+            return this.networkId.equals(that.networkId) &&
+                    this.rule.equals(that.rule);
+        } else {
+            return false;
+        }
+    }
+}
+
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchEvent.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchEvent.java
new file mode 100644
index 0000000..d1b5d57
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchEvent.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow rule batch event.
+ */
+public class VirtualFlowRuleBatchEvent {
+    NetworkId networkId;
+    FlowRuleBatchEvent event;
+
+    public VirtualFlowRuleBatchEvent(NetworkId networkId, FlowRuleBatchEvent event) {
+        this.networkId = networkId;
+        this.event = event;
+    }
+
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    public FlowRuleBatchEvent event() {
+        return event;
+    }
+
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, event);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof VirtualFlowRuleBatchEvent) {
+            VirtualFlowRuleBatchEvent that = (VirtualFlowRuleBatchEvent) other;
+            return this.networkId.equals(that.networkId) &&
+                    this.event.equals(that.event);
+        } else {
+            return false;
+        }
+    }
+}
+
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchOperation.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchOperation.java
new file mode 100644
index 0000000..8db1801
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/VirtualFlowRuleBatchOperation.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
+
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate flow rule batch operation.
+ */
+public class VirtualFlowRuleBatchOperation {
+    NetworkId networkId;
+    FlowRuleBatchOperation operation;
+
+    public VirtualFlowRuleBatchOperation(NetworkId networkId,
+                                         FlowRuleBatchOperation operation) {
+        this.networkId = networkId;
+        this.operation = operation;
+    }
+
+    public NetworkId networkId() {
+        return networkId;
+    }
+
+    public FlowRuleBatchOperation operation() {
+        return operation;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkId, operation);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this ==  other) {
+            return true;
+        }
+
+        if (other instanceof VirtualFlowRuleBatchOperation) {
+            VirtualFlowRuleBatchOperation that = (VirtualFlowRuleBatchOperation) other;
+            return this.networkId.equals(that.networkId) &&
+                    this.operation.equals(that.operation);
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/package-info.java b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/package-info.java
new file mode 100644
index 0000000..ee0de54
--- /dev/null
+++ b/apps/virtual/app/src/main/java/org/onosproject/incubator/net/virtual/store/impl/primitives/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of distributed virtual network store primitives.
+ */
+package org.onosproject.incubator.net.virtual.store.impl.primitives;
diff --git a/apps/virtual/app/src/main/resources/definitions/TenantId.json b/apps/virtual/app/src/main/resources/definitions/TenantId.json
new file mode 100644
index 0000000..237a9c4
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/TenantId.json
@@ -0,0 +1,14 @@
+{
+  "type": "object",
+  "title": "TenantId",
+  "required": [
+    "id"
+  ],
+  "properties": {
+    "id": {
+      "type": "String",
+      "description": "Tenant identifier",
+      "example": "Tenant123"
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/TenantIds.json b/apps/virtual/app/src/main/resources/definitions/TenantIds.json
new file mode 100644
index 0000000..f568b9f
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/TenantIds.json
@@ -0,0 +1,30 @@
+{
+  "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",
+            "description": "Tenant identifier",
+            "example": "Tenant123"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualDevice.json b/apps/virtual/app/src/main/resources/definitions/VirtualDevice.json
new file mode 100644
index 0000000..ada054c
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualDevice.json
@@ -0,0 +1,20 @@
+{
+  "type": "object",
+  "title": "vdev",
+  "required": [
+    "networkId",
+    "deviceId"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "int64",
+      "description": "Network identifier",
+      "example": 3
+    },
+    "deviceId": {
+      "type": "String",
+      "description": "Device identifier",
+      "example": "of:0000000000000042"
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualDevices.json b/apps/virtual/app/src/main/resources/definitions/VirtualDevices.json
new file mode 100644
index 0000000..61e4071
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualDevices.json
@@ -0,0 +1,36 @@
+{
+  "type": "object",
+  "title": "VirtualDevices",
+  "required": [
+    "devices"
+  ],
+  "properties": {
+    "devices": {
+      "type": "array",
+      "xml": {
+        "name": "devices",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "vdev",
+        "required": [
+          "networkId",
+          "deviceId"
+        ],
+        "properties": {
+          "networkId": {
+            "type": "int64",
+            "description": "Network identifier",
+            "example": 3
+          },
+          "deviceId": {
+            "type": "String",
+            "description": "Device identifier",
+            "example": "of:0000000000000042"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualHost.json b/apps/virtual/app/src/main/resources/definitions/VirtualHost.json
new file mode 100644
index 0000000..4035694
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualHost.json
@@ -0,0 +1,63 @@
+{
+  "type": "object",
+  "title": "host",
+  "required": [
+    "networkId",
+    "id",
+    "mac",
+    "vlan",
+    "ipAddresses",
+    "location"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "int64",
+      "description": "Network identifier",
+      "example": 3
+    },
+    "id": {
+      "type": "string",
+      "example": "46:E4:3C:A4:17:C8/-1"
+    },
+    "mac": {
+      "type": "string",
+      "example": "46:E4:3C:A4:17:C8"
+    },
+    "vlan": {
+      "type": "string",
+      "example": "-1"
+    },
+    "ipAddresses": {
+      "type": "array",
+      "xml": {
+        "name": "hosts",
+        "wrapped": true
+      },
+      "items": {
+        "type": "string",
+        "example": "127.0.0.1"
+      }
+    },
+    "locations": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "title": "location",
+        "required": [
+          "elementId",
+          "port"
+        ],
+        "properties": {
+          "elementId": {
+            "type": "string",
+            "example": "of:0000000000000002"
+          },
+          "port": {
+            "type": "string",
+            "example": "3"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualHostPut.json b/apps/virtual/app/src/main/resources/definitions/VirtualHostPut.json
new file mode 100644
index 0000000..3026478
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualHostPut.json
@@ -0,0 +1,58 @@
+{
+  "type": "object",
+  "title": "host",
+  "required": [
+    "networkId",
+    "mac",
+    "vlan",
+    "ipAddresses",
+    "location"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "int64",
+      "description": "Network identifier",
+      "example": 3
+    },
+    "mac": {
+      "type": "string",
+      "example": "46:E4:3C:A4:17:C8"
+    },
+    "vlan": {
+      "type": "string",
+      "example": "-1"
+    },
+    "ipAddresses": {
+      "type": "array",
+      "xml": {
+        "name": "hosts",
+        "wrapped": true
+      },
+      "items": {
+        "type": "string",
+        "example": "127.0.0.1"
+      }
+    },
+    "locations": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "title": "location",
+        "required": [
+          "elementId",
+          "port"
+        ],
+        "properties": {
+          "elementId": {
+            "type": "string",
+            "example": "of:0000000000000002"
+          },
+          "port": {
+            "type": "string",
+            "example": "3"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualHosts.json b/apps/virtual/app/src/main/resources/definitions/VirtualHosts.json
new file mode 100644
index 0000000..979a3f7
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualHosts.json
@@ -0,0 +1,79 @@
+{
+  "type": "object",
+  "title": "hosts",
+  "required": [
+    "hosts"
+  ],
+  "properties": {
+    "hosts": {
+      "type": "array",
+      "xml": {
+        "name": "hosts",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "host",
+        "required": [
+          "networkId",
+          "id",
+          "mac",
+          "vlan",
+          "ipAddresses",
+          "location"
+        ],
+        "properties": {
+          "networkId": {
+            "type": "int64",
+            "description": "Network identifier",
+            "example": 3
+          },
+          "id": {
+            "type": "string",
+            "example": "46:E4:3C:A4:17:C8/-1"
+          },
+          "mac": {
+            "type": "string",
+            "example": "46:E4:3C:A4:17:C8"
+          },
+          "vlan": {
+            "type": "string",
+            "example": "-1"
+          },
+          "ipAddresses": {
+            "type": "array",
+            "xml": {
+              "name": "hosts",
+              "wrapped": true
+            },
+            "items": {
+              "type": "string",
+              "example": "127.0.0.1"
+            }
+          },
+          "locations": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "title": "location",
+              "required": [
+                "elementId",
+                "port"
+              ],
+              "properties": {
+                "elementId": {
+                  "type": "string",
+                  "example": "of:0000000000000002"
+                },
+                "port": {
+                  "type": "string",
+                  "example": "3"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualLink.json b/apps/virtual/app/src/main/resources/definitions/VirtualLink.json
new file mode 100644
index 0000000..8550eb4
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualLink.json
@@ -0,0 +1,62 @@
+{
+  "type": "object",
+  "title": "vlink",
+  "required": [
+    "networkId",
+    "src",
+    "dst",
+    "type",
+    "state"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "int64",
+      "description": "Network identifier",
+      "example": 3
+    },
+    "src": {
+      "type": "object",
+      "title": "src",
+      "required": [
+        "port",
+        "device"
+      ],
+      "properties": {
+        "port": {
+          "type": "int64",
+          "example": 3
+        },
+        "device": {
+          "type": "string",
+          "example": "of:0000000000000002"
+        }
+      }
+    },
+    "dst": {
+      "type": "object",
+      "title": "dst",
+      "required": [
+        "port",
+        "device"
+      ],
+      "properties": {
+        "port": {
+          "type": "int64",
+          "example": 2
+        },
+        "device": {
+          "type": "string",
+          "example": "of:0000000000000003"
+        }
+      }
+    },
+    "type": {
+      "type": "string",
+      "example": "DIRECT"
+    },
+    "state": {
+      "type": "string",
+      "example": "ACTIVE"
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualLinks.json b/apps/virtual/app/src/main/resources/definitions/VirtualLinks.json
new file mode 100644
index 0000000..8163356
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualLinks.json
@@ -0,0 +1,78 @@
+{
+  "type": "object",
+  "title": "VirtualLinks",
+  "required": [
+    "links"
+  ],
+  "properties": {
+    "links": {
+      "type": "array",
+      "xml": {
+        "name": "links",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "vlink",
+        "required": [
+          "networkId",
+          "src",
+          "dst",
+          "type",
+          "state"
+        ],
+        "properties": {
+          "networkId": {
+            "type": "int64",
+            "description": "Network identifier",
+            "example": 3
+          },
+          "src": {
+            "type": "object",
+            "title": "src",
+            "required": [
+              "port",
+              "device"
+            ],
+            "properties": {
+              "port": {
+                "type": "int64",
+                "example": 3
+              },
+              "device": {
+                "type": "string",
+                "example": "of:0000000000000002"
+              }
+            }
+          },
+          "dst": {
+            "type": "object",
+            "title": "dst",
+            "required": [
+              "port",
+              "device"
+            ],
+            "properties": {
+              "port": {
+                "type": "int64",
+                "example": 2
+              },
+              "device": {
+                "type": "string",
+                "example": "of:0000000000000003"
+              }
+            }
+          },
+          "type": {
+            "type": "string",
+            "example": "VIRTUAL"
+          },
+          "state": {
+            "type": "string",
+            "example": "ACTIVE"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualNetworks.json b/apps/virtual/app/src/main/resources/definitions/VirtualNetworks.json
new file mode 100644
index 0000000..6ab1bde
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualNetworks.json
@@ -0,0 +1,36 @@
+{
+  "type": "object",
+  "title": "VirtualNetworks",
+  "required": [
+    "vnets"
+  ],
+  "properties": {
+    "vnets": {
+      "type": "array",
+      "xml": {
+        "name": "vnets",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "vnet",
+        "required": [
+          "networkId",
+          "tenantId"
+        ],
+        "properties": {
+          "networkId": {
+            "type": "int64",
+            "description": "Network identifier",
+            "example": 3
+          },
+          "tenantId": {
+            "type": "String",
+            "description": "Tenant identifier",
+            "example": "Tenant123"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualPort.json b/apps/virtual/app/src/main/resources/definitions/VirtualPort.json
new file mode 100644
index 0000000..d1b8e47
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualPort.json
@@ -0,0 +1,38 @@
+{
+  "type": "object",
+  "title": "vport",
+  "required": [
+    "networkId",
+    "deviceId",
+    "portNum",
+    "physDeviceId",
+    "physPortNum"
+  ],
+  "properties": {
+    "networkId": {
+      "type": "int64",
+      "description": "Network identifier",
+      "example": 3
+    },
+    "deviceId": {
+      "type": "String",
+      "description": "Virtual device identifier",
+      "example": "of:0000000000000042"
+    },
+    "portNum": {
+      "type": "int64",
+      "description": "Virtual device port number",
+      "example": 34
+    },
+    "physDeviceId": {
+      "type": "String",
+      "description": "Physical device identifier",
+      "example": "of:0000000000000003"
+    },
+    "physPortNum": {
+      "type": "int64",
+      "description": "Physical device port number",
+      "example": 2
+    }
+  }
+}
diff --git a/apps/virtual/app/src/main/resources/definitions/VirtualPorts.json b/apps/virtual/app/src/main/resources/definitions/VirtualPorts.json
new file mode 100644
index 0000000..daa5019
--- /dev/null
+++ b/apps/virtual/app/src/main/resources/definitions/VirtualPorts.json
@@ -0,0 +1,54 @@
+{
+  "type": "object",
+  "title": "VirtualPorts",
+  "required": [
+    "ports"
+  ],
+  "properties": {
+    "ports": {
+      "type": "array",
+      "xml": {
+        "name": "ports",
+        "wrapped": true
+      },
+      "items": {
+        "type": "object",
+        "title": "vport",
+        "required": [
+          "networkId",
+          "deviceId",
+          "portNum",
+          "physDeviceId",
+          "physPortNum"
+        ],
+        "properties": {
+          "networkId": {
+            "type": "int64",
+            "description": "Network identifier",
+            "example": 3
+          },
+          "deviceId": {
+            "type": "String",
+            "description": "Virtual device identifier",
+            "example": "of:0000000000000042"
+          },
+          "portNum": {
+            "type": "int64",
+            "description": "Virtual device port number",
+            "example": 34
+          },
+          "physDeviceId": {
+            "type": "String",
+            "description": "Physical device identifier",
+            "example": "of:0000000000000003"
+          },
+          "physPortNum": {
+            "type": "int64",
+            "description": "Physical device port number",
+            "example": 2
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/AbstractVirtualListenerManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/AbstractVirtualListenerManagerTest.java
new file mode 100644
index 0000000..bc0d32e
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/AbstractVirtualListenerManagerTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import com.google.common.collect.ClassToInstanceMap;
+import com.google.common.collect.MutableClassToInstanceMap;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventListener;
+import org.onosproject.event.EventSink;
+import org.onosproject.incubator.net.virtual.event.AbstractVirtualListenerManager;
+import org.onosproject.incubator.net.virtual.event.VirtualEvent;
+import org.onosproject.incubator.net.virtual.event.VirtualListenerRegistryManager;
+import org.onosproject.net.TenantId;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test of the virtual event dispatcher mechanism.
+ */
+public class AbstractVirtualListenerManagerTest {
+
+    private TestEventDispatcher dispatcher = new TestEventDispatcher();
+    private VirtualListenerRegistryManager listenerRegistryManager =
+            VirtualListenerRegistryManager.getInstance();
+
+    private PrickleManager prickleManager;
+    private PrickleListener prickleListener;
+
+    private GooManager gooManager;
+    private GooListener gooListener;
+
+    private BarManager barManager;
+    private BarListener barListener;
+
+    @Before
+    public void setUp() {
+        VirtualNetworkService manager = new TestVirtualNetworkManager();
+
+        dispatcher.addSink(VirtualEvent.class, listenerRegistryManager);
+
+        prickleListener = new PrickleListener();
+        prickleManager = new PrickleManager(manager, NetworkId.networkId(1));
+        prickleManager.addListener(prickleListener);
+
+        gooListener = new GooListener();
+        gooManager = new GooManager(manager, NetworkId.networkId(1));
+        gooManager.addListener(gooListener);
+
+        barListener = new BarListener();
+        barManager = new BarManager(manager, NetworkId.networkId(2));
+        barManager.addListener(barListener);
+    }
+
+    @After
+    public void tearDown() {
+        dispatcher.removeSink(VirtualEvent.class);
+
+        prickleListener.events.clear();
+        gooListener.events.clear();
+        barListener.events.clear();
+
+        prickleListener.latch = null;
+        gooListener.latch = null;
+        barListener.latch = null;
+    }
+
+    @Test
+    public void postPrickle() throws InterruptedException {
+        prickleListener.latch = new CountDownLatch(1);
+        prickleManager.post(new Prickle("prickle"));
+        prickleListener.latch.await(100, TimeUnit.MILLISECONDS);
+
+        validate(prickleListener, "prickle");
+        validate(gooListener);
+        validate(barListener);
+    }
+
+    @Test
+    public void postGoo() throws InterruptedException {
+        gooListener.latch = new CountDownLatch(1);
+        gooManager.post(new Goo("goo"));
+        gooListener.latch.await(100, TimeUnit.MILLISECONDS);
+
+        validate(prickleListener);
+        validate(gooListener, "goo");
+        validate(barListener);
+    }
+
+    @Test
+    public void postBar() throws InterruptedException {
+        barListener.latch = new CountDownLatch(1);
+        barManager.post(new Bar("bar"));
+        barListener.latch.await(100, TimeUnit.MILLISECONDS);
+
+        validate(prickleListener);
+        validate(gooListener);
+        validate(barListener, "bar");
+    }
+
+    @Test
+    public void postEventWithNoListener() throws Exception {
+        dispatcher.post(new Thing("boom"));
+
+        validate(prickleListener);
+        validate(gooListener);
+        validate(barListener);
+    }
+
+    private void validate(TestListener listener, String... strings) {
+        int i = 0;
+        assertEquals("incorrect event count", strings.length, listener.events.size());
+        for (String string : strings) {
+            Event event = (Event) listener.events.get(i++);
+            assertEquals("incorrect event", string, event.subject());
+        }
+    }
+
+    private enum Type { FOO }
+
+    private static class Thing extends AbstractEvent<Type, String> {
+        protected Thing(String subject) {
+            super(Type.FOO, subject);
+        }
+    }
+
+    private static final class Prickle extends Thing {
+        private Prickle(String subject) {
+            super(subject);
+        }
+    }
+
+    private static final class Goo extends Thing {
+        private Goo(String subject) {
+            super(subject);
+        }
+    }
+
+    private static final class Bar extends Thing {
+        private Bar(String subject) {
+            super(subject);
+        }
+    }
+
+    private class TestListener<E extends Event> implements EventListener<E> {
+        List<E> events = new ArrayList<>();
+        CountDownLatch latch;
+
+        @Override
+        public void event(E event) {
+            events.add(event);
+            latch.countDown();
+        }
+    }
+
+    private class PrickleListener extends TestListener<Prickle> {
+    }
+
+    private class GooListener extends TestListener<Goo> {
+    }
+
+    private class BarListener extends TestListener<Bar> {
+    }
+
+    private class PrickleManager extends AbstractVirtualListenerManager<Prickle, PrickleListener> {
+        public PrickleManager(VirtualNetworkService service, NetworkId networkId) {
+            super(service, networkId, Prickle.class);
+        }
+
+        @Override
+        protected void post(Prickle event) {
+            super.post(event);
+        }
+    }
+
+    private class GooManager extends AbstractVirtualListenerManager<Goo, GooListener> {
+        public GooManager(VirtualNetworkService service, NetworkId networkId) {
+            super(service, networkId, Goo.class);
+        }
+
+        @Override
+        protected void post(Goo event) {
+            super.post(event);
+        }
+    }
+
+    private class BarManager extends AbstractVirtualListenerManager<Bar, BarListener> {
+        public BarManager(VirtualNetworkService service, NetworkId networkId) {
+            super(service, networkId, Bar.class);
+        }
+
+        @Override
+        protected void post(Bar event) {
+            super.post(event);
+        }
+    }
+
+
+    private class TestEventDispatcher implements EventDeliveryService {
+        private EventSink sink;
+
+        @Override
+        public <E extends Event> void addSink(Class<E> eventClass, EventSink<E> sink) {
+            this.sink = sink;
+        }
+
+        @Override
+        public <E extends Event> void removeSink(Class<E> eventClass) {
+            this.sink = null;
+        }
+
+        @Override
+        public <E extends Event> EventSink<E> getSink(Class<E> eventClass) {
+            return null;
+        }
+
+        @Override
+        public Set<Class<? extends Event>> getSinks() {
+            return null;
+        }
+
+        @Override
+        public void setDispatchTimeLimit(long millis) {
+
+        }
+
+        @Override
+        public long getDispatchTimeLimit() {
+            return 0;
+        }
+
+        @Override
+        public void post(Event event) {
+            if (event instanceof VirtualEvent) {
+                sink.process(event);
+            }
+        }
+    }
+
+    private class TestVirtualNetworkManager extends VirtualNetworkServiceAdapter {
+        TestServiceDirectory serviceDirectory = new TestServiceDirectory();
+
+        public TestVirtualNetworkManager() {
+            serviceDirectory.add(EventDeliveryService.class, dispatcher);
+        }
+
+        @Override
+        public VirtualNetwork getVirtualNetwork(NetworkId networkId) {
+            return null;
+        }
+
+        @Override
+        public TenantId getTenantId(NetworkId networkId) {
+            return null;
+        }
+
+        @Override
+        public ServiceDirectory getServiceDirectory() {
+            return serviceDirectory;
+        }
+    }
+
+    private  class TestServiceDirectory implements ServiceDirectory {
+
+        private ClassToInstanceMap<Object> services = MutableClassToInstanceMap.create();
+
+        @Override
+        public <T> T get(Class<T> serviceClass) {
+            return services.getInstance(serviceClass);
+        }
+
+        /**
+         * Adds a new service to the directory.
+         *
+         * @param serviceClass service class
+         * @param service service instance
+         * @return self
+         */
+        public TestServiceDirectory add(Class serviceClass, Object service) {
+            services.putInstance(serviceClass, service);
+            return this;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/VirtualNetworkAdminServiceAdapter.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/VirtualNetworkAdminServiceAdapter.java
new file mode 100644
index 0000000..4fbed2f
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/VirtualNetworkAdminServiceAdapter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TenantId;
+
+import java.util.Set;
+
+/**
+ * Test adapter for virtual network admin service.
+ */
+public class VirtualNetworkAdminServiceAdapter
+        extends VirtualNetworkServiceAdapter
+        implements VirtualNetworkAdminService {
+
+    @Override
+    public void registerTenantId(TenantId tenantId) {
+
+    }
+
+    @Override
+    public void unregisterTenantId(TenantId tenantId) {
+
+    }
+
+    @Override
+    public Set<TenantId> getTenantIds() {
+        return null;
+    }
+
+    @Override
+    public VirtualNetwork createVirtualNetwork(TenantId tenantId) {
+        return null;
+    }
+
+    @Override
+    public void removeVirtualNetwork(NetworkId networkId) {
+
+    }
+
+    @Override
+    public VirtualDevice createVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public void removeVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+
+    }
+
+    @Override
+    public VirtualHost createVirtualHost(NetworkId networkId, HostId hostId,
+                                         MacAddress mac, VlanId vlan,
+                                         HostLocation location, Set<IpAddress> ips) {
+        return null;
+    }
+
+    @Override
+    public void removeVirtualHost(NetworkId networkId, HostId hostId) {
+
+    }
+
+    @Override
+    public VirtualLink createVirtualLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+        return null;
+    }
+
+    @Override
+    public void removeVirtualLink(NetworkId networkId, ConnectPoint src, ConnectPoint dst) {
+
+    }
+
+    @Override
+    public VirtualPort createVirtualPort(NetworkId networkId, DeviceId deviceId,
+                                         PortNumber portNumber, ConnectPoint realizedBy) {
+        return null;
+    }
+
+    @Override
+    public void bindVirtualPort(NetworkId networkId, DeviceId deviceId,
+                                PortNumber portNumber, ConnectPoint realizedBy) {
+
+    }
+
+    @Override
+    public void updatePortState(NetworkId networkId, DeviceId deviceId, PortNumber portNumber, boolean isEnabled) {
+
+    }
+
+    @Override
+    public void removeVirtualPort(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+
+    }
+
+    @Override
+    public VirtualNetwork getVirtualNetwork(NetworkId networkId) {
+        return null;
+    }
+
+    @Override
+    public TenantId getTenantId(NetworkId networkId) {
+        return null;
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/VirtualNetworkServiceAdapter.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/VirtualNetworkServiceAdapter.java
new file mode 100644
index 0000000..9420d60
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/VirtualNetworkServiceAdapter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual;
+
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.TenantId;
+
+import java.util.Set;
+
+/**
+ * Test adapter for virtual network service.
+ */
+public abstract class VirtualNetworkServiceAdapter implements VirtualNetworkService {
+    @Override
+    public void addListener(VirtualNetworkListener listener) {
+
+    }
+
+    @Override
+    public void removeListener(VirtualNetworkListener listener) {
+
+    }
+
+    @Override
+    public Set<VirtualNetwork> getVirtualNetworks(TenantId tenantId) {
+        return null;
+    }
+
+    @Override
+    public Set<VirtualDevice> getVirtualDevices(NetworkId networkId) {
+        return null;
+    }
+
+    @Override
+    public Set<VirtualHost> getVirtualHosts(NetworkId networkId) {
+        return null;
+    }
+
+    @Override
+    public Set<VirtualLink> getVirtualLinks(NetworkId networkId) {
+        return null;
+    }
+
+    @Override
+    public Set<VirtualPort> getVirtualPorts(NetworkId networkId, DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public Set<DeviceId> getPhysicalDevices(NetworkId networkId, DeviceId deviceId) {
+        return null;
+    }
+
+    @Override
+    public <T> T get(NetworkId networkId, Class<T> serviceClass) {
+        return null;
+    }
+
+    @Override
+    public ServiceDirectory getServiceDirectory() {
+        return null;
+    }
+
+    @Override
+    public ApplicationId getVirtualNetworkApplicationId(NetworkId networkId) {
+        return null;
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/TestCoreService.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/TestCoreService.java
new file mode 100644
index 0000000..80b74df
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/TestCoreService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Core service test class.
+ */
+class TestCoreService extends CoreServiceAdapter {
+
+    @Override
+    public IdGenerator getIdGenerator(String topic) {
+        return new IdGenerator() {
+            private AtomicLong counter = new AtomicLong(0);
+
+            @Override
+            public long getNewId() {
+                return counter.getAndIncrement();
+            }
+        };
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManagerTest.java
new file mode 100644
index 0000000..63681e8
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManagerTest.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestTools;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.net.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.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.*;
+
+/**
+ * Junit tests for VirtualNetworkDeviceService.
+ */
+public class VirtualNetworkDeviceManagerTest extends VirtualNetworkTestUtil {
+    private final String tenantIdValue1 = "TENANT_ID1";
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private CoreService coreService;
+    private TestServiceDirectory testDirectory;
+    private TestListener testListener = new TestListener();
+    private TestEventDispatcher dispatcher = new TestEventDispatcher();
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        coreService = new VirtualNetworkDeviceManagerTest.TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, dispatcher);
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+    }
+
+    /**
+     * Tests the getDevices(), getAvailableDevices(), getDeviceCount(), getDevice(), and isAvailable() methods.
+     */
+    @Test
+    public void testGetDevices() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice device1 = manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice device2 = manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getDevices() method
+        Iterator<Device> it = deviceService.getDevices().iterator();
+        assertEquals("The device set size did not match.", 2, Iterators.size(it));
+
+        // test the getAvailableDevices() method
+        Iterator<Device> it2 = deviceService.getAvailableDevices().iterator();
+        assertEquals("The device set size did not match.", 2, Iterators.size(it2));
+
+        // test the getDeviceCount() method
+        assertEquals("The device set size did not match.", 2, deviceService.getDeviceCount());
+
+        // test the getDevice() method
+        assertEquals("The expect device did not match.", device1,
+                     deviceService.getDevice(DID1));
+        assertNotEquals("The expect device should not have matched.", device1,
+                        deviceService.getDevice(DID2));
+
+        // test the isAvailable() method
+        assertTrue("The expect device availability did not match.",
+                   deviceService.isAvailable(DID1));
+        assertFalse("The expect device availability did not match.",
+                    deviceService.isAvailable(DID3));
+    }
+
+    /**
+     * Tests querying for a device using a null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDeviceByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getDevice() method with null device id value.
+        deviceService.getDevice(null);
+    }
+
+    /**
+     * Tests querying for a device using a null device type.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDeviceByNullType() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getDevices() method with null type value.
+        deviceService.getDevices(null);
+    }
+
+    /**
+     * Tests the isAvailable method using a null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsAvailableByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the isAvailable() method with null device id value.
+        deviceService.isAvailable(null);
+    }
+
+    /**
+     * Tests querying for a device and available devices by device type.
+     */
+    @Test
+    public void testGetDeviceType() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getDevices(Type) method.
+        Iterator<Device> it = deviceService.getDevices(Device.Type.VIRTUAL).iterator();
+        assertEquals("The device set size did not match.", 2, Iterators.size(it));
+        Iterator<Device> it2 = deviceService.getDevices(Device.Type.SWITCH).iterator();
+        assertEquals("The device set size did not match.", 0, Iterators.size(it2));
+
+        // test the getAvailableDevices(Type) method.
+        Iterator<Device> it3 = deviceService.getAvailableDevices(Device.Type.VIRTUAL).iterator();
+        assertEquals("The device set size did not match.", 2, Iterators.size(it3));
+    }
+
+    /**
+     * Tests querying the role of a device by null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetRoleByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getRole() method using a null device identifier
+        deviceService.getRole(null);
+    }
+
+    /**
+     * Tests querying the role of a device by device identifier.
+     */
+    @Test
+    public void testGetRole() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getRole() method
+        assertEquals("The expect device role did not match.", MastershipRole.MASTER,
+                     deviceService.getRole(DID1));
+    }
+
+    /**
+     * Tests querying the ports of a device by null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPortsByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getPorts() method using a null device identifier
+        deviceService.getPorts(null);
+    }
+
+    /**
+     * Tests querying the ports of a device by device identifier.
+     */
+    @Test
+    public void testGetPorts() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice = manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        ConnectPoint cp = new ConnectPoint(virtualDevice.id(), PortNumber.portNumber(1));
+
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice.id(), PortNumber.portNumber(1), cp);
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice.id(), PortNumber.portNumber(2), cp);
+
+        // test the getPorts() method
+        assertEquals("The port set size did not match.", 2,
+                     deviceService.getPorts(DID1).size());
+        assertEquals("The port set size did not match.", 0,
+                     deviceService.getPorts(DID2).size());
+    }
+
+    /**
+     * Tests querying the port of a device by device identifier and port number.
+     */
+    @Test
+    public void testGetPort() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice = manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        ConnectPoint cp = new ConnectPoint(virtualDevice.id(), PortNumber.portNumber(1));
+
+        VirtualPort virtualPort1 = manager.createVirtualPort(virtualNetwork.id(), virtualDevice.id(),
+                                                             PortNumber.portNumber(1), cp);
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice.id(), PortNumber.portNumber(2), cp);
+
+        // test the getPort() method
+        assertEquals("The port did not match as expected.", virtualPort1,
+                     deviceService.getPort(DID1, PortNumber.portNumber(1)));
+        assertNotEquals("The port did not match as expected.", virtualPort1,
+                        deviceService.getPort(DID1, PortNumber.portNumber(3)));
+    }
+
+    /**
+     * Tests querying the port statistics of a device by null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPortsStatisticsByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getPortStatistics() method using a null device identifier
+        deviceService.getPortStatistics(null);
+    }
+
+    /**
+     * Tests querying the port statistics of a device by device identifier.
+     */
+    @Test
+    public void testGetPortStatistics() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice = manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getPortStatistics() method
+        assertEquals("The port statistics set size did not match.", 0,
+                     deviceService.getPortStatistics(DID1).size());
+    }
+
+    /**
+     * Tests querying the port delta statistics of a device by null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPortsDeltaStatisticsByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getPortDeltaStatistics() method using a null device identifier
+        deviceService.getPortDeltaStatistics(null);
+    }
+
+    /**
+     * Tests querying the port delta statistics of a device by device identifier.
+     */
+    @Test
+    public void testGetPortDeltaStatistics() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice = manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // test the getPortDeltaStatistics() method
+        assertEquals("The port delta statistics set size did not match.", 0,
+                     deviceService.getPortDeltaStatistics(DID1).size());
+    }
+
+    /**
+     * Tests DeviceEvents received during virtual device/port addition and removal.
+     */
+    @Test
+    public void testDeviceEventsForAddRemovalDeviceAndPorts() throws TestUtils.TestUtilsException {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        // add virtual device before virtual device manager is created
+        VirtualDevice device1 = manager.createVirtualDevice(virtualNetwork.id(), VDID1);
+        validateEvents(); // no DeviceEvent expected
+
+        testDirectory.add(EventDeliveryService.class, dispatcher);
+        DeviceService deviceService = manager.get(virtualNetwork.id(), DeviceService.class);
+
+        // virtual device manager is created; register DeviceEvent listener
+        deviceService.addListener(testListener);
+
+        // list to keep track of expected event types
+        List<DeviceEvent.Type> expectedEventTypes = new ArrayList<>();
+
+        // add virtual device
+        VirtualDevice device2 = manager.createVirtualDevice(virtualNetwork.id(), VDID2);
+        expectedEventTypes.add(DeviceEvent.Type.DEVICE_ADDED);
+
+        ConnectPoint cp = new ConnectPoint(PHYDID1, PortNumber.portNumber(1));
+
+        // add 2 virtual ports
+        manager.createVirtualPort(virtualNetwork.id(),
+                                  device2.id(), PortNumber.portNumber(1), cp);
+        expectedEventTypes.add(DeviceEvent.Type.PORT_ADDED);
+        manager.createVirtualPort(virtualNetwork.id(),
+                                  device2.id(), PortNumber.portNumber(2), cp);
+        expectedEventTypes.add(DeviceEvent.Type.PORT_ADDED);
+
+        // verify virtual ports were added
+        Set<VirtualPort> virtualPorts = manager.getVirtualPorts(virtualNetwork.id(), device2.id());
+        assertNotNull("The virtual port set should not be null", virtualPorts);
+        assertEquals("The virtual port set size did not match.", 2, virtualPorts.size());
+        virtualPorts.forEach(vp -> assertFalse("Initial virtual port state should be disabled", vp.isEnabled()));
+
+        // verify change state of virtual port (disabled -> enabled)
+        manager.updatePortState(virtualNetwork.id(), device2.id(), PortNumber.portNumber(1), true);
+        Port changedPort = deviceService.getPort(device2.id(), PortNumber.portNumber(1));
+        assertNotNull("The changed virtual port should not be null", changedPort);
+        assertEquals("Virtual port state should be enabled", true, changedPort.isEnabled());
+        expectedEventTypes.add(DeviceEvent.Type.PORT_UPDATED);
+
+        // verify change state of virtual port (disabled -> disabled)
+        manager.updatePortState(virtualNetwork.id(), device2.id(), PortNumber.portNumber(2), false);
+        changedPort = deviceService.getPort(device2.id(), PortNumber.portNumber(2));
+        assertNotNull("The changed virtual port should not be null", changedPort);
+        assertEquals("Virtual port state should be disabled", false, changedPort.isEnabled());
+        // no VIRTUAL_PORT_UPDATED event is expected - the requested state (disabled) is same as previous state.
+
+        // remove 2 virtual ports
+        for (VirtualPort virtualPort : virtualPorts) {
+            manager.removeVirtualPort(virtualNetwork.id(),
+                                      (DeviceId) virtualPort.element().id(), virtualPort.number());
+            expectedEventTypes.add(DeviceEvent.Type.PORT_REMOVED);
+            // attempt to remove the same virtual port again - no DeviceEvent.Type.PORT_REMOVED expected.
+            manager.removeVirtualPort(virtualNetwork.id(),
+                                      (DeviceId) virtualPort.element().id(), virtualPort.number());
+        }
+
+        // verify virtual ports were removed
+        virtualPorts = manager.getVirtualPorts(virtualNetwork.id(), device2.id());
+        assertTrue("The virtual port set should be empty.", virtualPorts.isEmpty());
+
+        // Add/remove one virtual port again.
+        VirtualPort virtualPort =
+                manager.createVirtualPort(virtualNetwork.id(), device2.id(),
+                                                            PortNumber.portNumber(1), cp);
+        expectedEventTypes.add(DeviceEvent.Type.PORT_ADDED);
+
+        ConnectPoint newCp = new ConnectPoint(PHYDID3, PortNumber.portNumber(2));
+        manager.bindVirtualPort(virtualNetwork.id(), device2.id(),
+                                PortNumber.portNumber(1), newCp);
+        expectedEventTypes.add(DeviceEvent.Type.PORT_UPDATED);
+
+        manager.removeVirtualPort(virtualNetwork.id(),
+                                  (DeviceId) virtualPort.element().id(), virtualPort.number());
+        expectedEventTypes.add(DeviceEvent.Type.PORT_REMOVED);
+
+        // verify no virtual ports remain
+        virtualPorts = manager.getVirtualPorts(virtualNetwork.id(), device2.id());
+        assertTrue("The virtual port set should be empty.", virtualPorts.isEmpty());
+
+        // remove virtual device
+        manager.removeVirtualDevice(virtualNetwork.id(), device2.id());
+        expectedEventTypes.add(DeviceEvent.Type.DEVICE_REMOVED);
+
+        // Validate that the events were all received in the correct order.
+        validateEvents((Enum[]) expectedEventTypes.toArray(
+                new DeviceEvent.Type[expectedEventTypes.size()]));
+
+        // cleanup
+        deviceService.removeListener(testListener);
+    }
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+    }
+
+    /**
+     * Method to validate that the actual versus expected virtual network events were
+     * received correctly.
+     *
+     * @param types expected virtual network events.
+     */
+    private void validateEvents(Enum... types) {
+        TestTools.assertAfter(100, () -> {
+            int i = 0;
+            assertEquals("wrong events received", types.length, testListener.events.size());
+            for (Event event : testListener.events) {
+                assertEquals("incorrect event type", types[i], event.type());
+                i++;
+            }
+            testListener.events.clear();
+        });
+    }
+
+    /**
+     * Test listener class to receive device events.
+     */
+    private static class TestListener implements DeviceListener {
+
+        private List<DeviceEvent> events = Lists.newArrayList();
+
+        @Override
+        public void event(DeviceEvent event) {
+            events.add(event);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java
new file mode 100644
index 0000000..1def419
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.TestApplicationId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.impl.provider.VirtualProviderManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowRuleStore;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TestStorageService;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Junit tests for VirtualNetworkFlowObjectiveManager.
+ */
+public class VirtualNetworkFlowObjectiveManagerTest
+        extends VirtualNetworkTestUtil {
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private ServiceDirectory testDirectory;
+    protected SimpleVirtualFlowObjectiveStore flowObjectiveStore;
+
+    private VirtualProviderManager providerRegistryService;
+    private EventDeliveryService eventDeliveryService;
+
+    private ApplicationId appId;
+
+    private VirtualNetwork vnet1;
+    private VirtualNetwork vnet2;
+
+    private FlowObjectiveService service1;
+    private FlowObjectiveService service2;
+
+    //FIXME: referring flowrule service, store, and provider shouldn't be here
+    private VirtualFlowRuleProvider flowRuleProvider = new TestProvider();
+    private SimpleVirtualFlowRuleStore flowRuleStore;
+    protected StorageService storageService = new TestStorageService();
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        CoreService coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", storageService);
+        virtualNetworkManagerStore.activate();
+
+        flowObjectiveStore = new SimpleVirtualFlowObjectiveStore();
+        TestUtils.setField(flowObjectiveStore, "storageService", storageService);
+        flowObjectiveStore.activate();
+        flowRuleStore = new SimpleVirtualFlowRuleStore();
+        flowRuleStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        TestUtils.setField(manager, "coreService", coreService);
+
+        providerRegistryService = new VirtualProviderManager();
+        providerRegistryService.registerProvider(flowRuleProvider);
+
+        eventDeliveryService = new TestEventDispatcher();
+        NetTestTools.injectEventDispatcher(manager, eventDeliveryService);
+
+        appId = new TestApplicationId("FlowRuleManagerTest");
+
+        testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(CoreService.class, coreService)
+                .add(EventDeliveryService.class, eventDeliveryService)
+                .add(VirtualProviderRegistryService.class, providerRegistryService)
+                .add(VirtualNetworkFlowRuleStore.class, flowRuleStore)
+                .add(VirtualNetworkFlowObjectiveStore.class, flowObjectiveStore);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+
+        vnet1 = setupVirtualNetworkTopology(manager, TID1);
+        vnet2 = setupVirtualNetworkTopology(manager, TID2);
+
+        service1 = new VirtualNetworkFlowObjectiveManager(manager, vnet1.id());
+        service2 = new VirtualNetworkFlowObjectiveManager(manager, vnet2.id());
+    }
+
+    @After
+    public void tearDownTest() {
+        manager.deactivate();
+        virtualNetworkManagerStore.deactivate();
+    }
+
+    /**
+     * Tests adding a forwarding objective.
+     */
+    @Test
+    public void forwardingObjective() {
+        TrafficSelector selector = DefaultTrafficSelector.emptySelector();
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        ForwardingObjective forward =
+                DefaultForwardingObjective.builder()
+                        .fromApp(NetTestTools.APP_ID)
+                        .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                        .withSelector(selector)
+                        .withTreatment(treatment)
+                        .makePermanent()
+                        .add(new ObjectiveContext() {
+                            @Override
+                            public void onSuccess(Objective objective) {
+                                assertEquals("1 flowrule entry expected",
+                                             1, flowRuleStore.getFlowRuleCount(vnet1.id()));
+                                assertEquals("0 flowrule entry expected",
+                                             0, flowRuleStore.getFlowRuleCount(vnet2.id()));
+                            }
+                        });
+
+        service1.forward(VDID1, forward);
+    }
+
+    /**
+     * Tests adding a next objective.
+     */
+    @Test
+    public void nextObjective() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(service1.allocateNextId())
+                .fromApp(NetTestTools.APP_ID)
+                .addTreatment(treatment)
+                .withType(NextObjective.Type.BROADCAST)
+                .makePermanent()
+                .add(new ObjectiveContext() {
+                    @Override
+                    public void onSuccess(Objective objective) {
+                        assertEquals("1 next map entry expected",
+                                     1, service1.getNextMappings().size());
+                        assertEquals("0 next map entry expected",
+                                     0, service2.getNextMappings().size());
+                    }
+                });
+
+        service1.next(VDID1, nextObjective);
+    }
+
+    /**
+     * Tests adding a filtering objective.
+     */
+    @Test
+    public void filteringObjective() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        FilteringObjective filter =
+                DefaultFilteringObjective.builder()
+                        .fromApp(NetTestTools.APP_ID)
+                        .withMeta(treatment)
+                        .makePermanent()
+                        .deny()
+                        .addCondition(Criteria.matchEthType(12))
+                        .add(new ObjectiveContext() {
+                            @Override
+                            public void onSuccess(Objective objective) {
+                                assertEquals("1 flowrule entry expected",
+                                             1,
+                                             flowRuleStore.getFlowRuleCount(vnet1.id()));
+                                assertEquals("0 flowrule entry expected",
+                                             0,
+                                             flowRuleStore.getFlowRuleCount(vnet2.id()));
+
+                            }
+                        });
+
+        service1.filter(VDID1, filter);
+    }
+
+    //TODO: More test cases for filter, forward, and next
+
+    private class TestProvider extends AbstractVirtualProvider
+            implements VirtualFlowRuleProvider {
+
+        protected TestProvider() {
+            super(new ProviderId("test", "org.onosproject.virtual.testprovider"));
+        }
+
+        @Override
+        public void applyFlowRule(NetworkId networkId, FlowRule... flowRules) {
+
+        }
+
+        @Override
+        public void removeFlowRule(NetworkId networkId, FlowRule... flowRules) {
+
+        }
+
+        @Override
+        public void executeBatch(NetworkId networkId, FlowRuleBatchOperation batch) {
+
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerWithDistStoreTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerWithDistStoreTest.java
new file mode 100644
index 0000000..78ed454
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowObjectiveManagerWithDistStoreTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.onlab.junit.TestUtils;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualFlowObjectiveStore;
+
+/**
+ * Junit tests for VirtualNetworkFlowObjectiveManager using
+ * DistributedVirtualFlowObjectiveStore.  This test class extends
+ * VirtualNetworkFlowObjectiveManagerTest - all the tests defined in
+ * VirtualNetworkFlowObjectiveManagerTest will run using
+ * DistributedVirtualFlowObjectiveStore.
+ */
+public class VirtualNetworkFlowObjectiveManagerWithDistStoreTest
+        extends VirtualNetworkFlowObjectiveManagerTest {
+
+    private static final String STORE_FIELDNAME_STORAGESERVICE = "storageService";
+
+    private DistributedVirtualFlowObjectiveStore distStore;
+
+    @Before
+    public void setUp() throws Exception {
+        setupDistFlowObjectiveStore();
+        super.setUp();
+    }
+
+    private void setupDistFlowObjectiveStore() throws TestUtils.TestUtilsException {
+        distStore = new DistributedVirtualFlowObjectiveStore();
+        TestUtils.setField(distStore, STORE_FIELDNAME_STORAGESERVICE, storageService);
+
+        distStore.activate();
+        flowObjectiveStore = distStore; // super.setUp() will cause Distributed store to be used.
+    }
+
+    @After
+    public void tearDown() {
+        distStore.deactivate();
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java
new file mode 100644
index 0000000..037b67f
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkFlowRuleManagerTest.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.TestApplicationId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.impl.provider.VirtualProviderManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowRuleStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.StoredFlowEntry;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.*;
+
+public class VirtualNetworkFlowRuleManagerTest extends VirtualNetworkTestUtil {
+    private static final int TIMEOUT = 10;
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private ServiceDirectory testDirectory;
+    private VirtualNetworkFlowRuleStore flowRuleStore;
+    private VirtualProviderManager providerRegistryService;
+
+    private EventDeliveryService eventDeliveryService;
+
+    private VirtualNetworkFlowRuleManager vnetFlowRuleService1;
+    private VirtualNetworkFlowRuleManager vnetFlowRuleService2;
+
+    private VirtualFlowRuleProvider provider = new TestProvider();
+    private VirtualFlowRuleProviderService providerService1;
+    private VirtualFlowRuleProviderService providerService2;
+
+    protected TestFlowRuleListener listener1 = new TestFlowRuleListener();
+    protected TestFlowRuleListener listener2 = new TestFlowRuleListener();
+
+    private VirtualNetwork vnet1;
+    private VirtualNetwork vnet2;
+
+    private ApplicationId appId;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        CoreService coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        flowRuleStore = new SimpleVirtualFlowRuleStore();
+
+        providerRegistryService = new VirtualProviderManager();
+        providerRegistryService.registerProvider(provider);
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        TestUtils.setField(manager, "coreService", coreService);
+
+        eventDeliveryService = new TestEventDispatcher();
+        NetTestTools.injectEventDispatcher(manager, eventDeliveryService);
+
+        appId = new TestApplicationId("FlowRuleManagerTest");
+
+        testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(CoreService.class, coreService)
+                .add(VirtualProviderRegistryService.class, providerRegistryService)
+                .add(EventDeliveryService.class, eventDeliveryService)
+                .add(VirtualNetworkFlowRuleStore.class, flowRuleStore);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+
+        vnet1 = setupVirtualNetworkTopology(manager, TID1);
+        vnet2 = setupVirtualNetworkTopology(manager, TID2);
+
+        vnetFlowRuleService1 = new VirtualNetworkFlowRuleManager(manager, vnet1.id());
+        vnetFlowRuleService2 = new VirtualNetworkFlowRuleManager(manager, vnet2.id());
+        vnetFlowRuleService1.addListener(listener1);
+        vnetFlowRuleService2.addListener(listener2);
+
+        vnetFlowRuleService1.operationsService = MoreExecutors.newDirectExecutorService();
+        vnetFlowRuleService2.operationsService = MoreExecutors.newDirectExecutorService();
+        vnetFlowRuleService1.deviceInstallers = MoreExecutors.newDirectExecutorService();
+        vnetFlowRuleService2.deviceInstallers = MoreExecutors.newDirectExecutorService();
+
+        providerService1 = (VirtualFlowRuleProviderService)
+                providerRegistryService.getProviderService(vnet1.id(), VirtualFlowRuleProvider.class);
+        providerService2 = (VirtualFlowRuleProviderService)
+                providerRegistryService.getProviderService(vnet2.id(), VirtualFlowRuleProvider.class);
+    }
+
+    @After
+    public void tearDown() {
+        manager.deactivate();
+        virtualNetworkManagerStore.deactivate();
+    }
+
+    private FlowRule flowRule(int tsval, int trval) {
+        return flowRule(VDID1, tsval, trval);
+    }
+
+    private FlowRule flowRule(DeviceId did, int tsval, int trval) {
+        TestSelector ts = new TestSelector(tsval);
+        TestTreatment tr = new TestTreatment(trval);
+        return DefaultFlowRule.builder()
+                .forDevice(did)
+                .withSelector(ts)
+                .withTreatment(tr)
+                .withPriority(10)
+                .fromApp(appId)
+                .makeTemporary(TIMEOUT)
+                .build();
+    }
+
+    private FlowRule addFlowRule(int hval) {
+        FlowRule rule = flowRule(hval, hval);
+        vnetFlowRuleService1.applyFlowRules(rule);
+
+        assertNotNull("rule should be found", vnetFlowRuleService1.getFlowEntries(VDID1));
+        return rule;
+    }
+
+    private int flowCount(FlowRuleService service) {
+        List<FlowEntry> entries = Lists.newArrayList();
+        service.getFlowEntries(VDID1).forEach(entries::add);
+        return entries.size();
+    }
+
+    @Test
+    public void getFlowEntries() {
+        assertTrue("store should be empty",
+                   Sets.newHashSet(vnetFlowRuleService1.getFlowEntries(VDID1)).isEmpty());
+        assertTrue("store should be empty",
+                   Sets.newHashSet(vnetFlowRuleService2.getFlowEntries(VDID1)).isEmpty());
+
+        FlowRule f1 = addFlowRule(1);
+        FlowRule f2 = addFlowRule(2);
+
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+
+        assertEquals("2 rules should exist", 2, flowCount(vnetFlowRuleService1));
+        assertEquals("0 rules should exist", 0, flowCount(vnetFlowRuleService2));
+
+        providerService1.pushFlowMetrics(VDID1, ImmutableList.of(fe1, fe2));
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+                       RULE_ADDED, RULE_ADDED);
+
+        addFlowRule(1);
+        assertEquals("should still be 2 rules", 2, flowCount(vnetFlowRuleService1));
+        System.err.println("events :" + listener1.events);
+        assertEquals("0 rules should exist", 0, flowCount(vnetFlowRuleService2));
+
+        providerService1.pushFlowMetrics(VDID1, ImmutableList.of(fe1));
+        validateEvents(listener1, RULE_UPDATED, RULE_UPDATED);
+    }
+
+    @Test
+    public void applyFlowRules() {
+        FlowRule r1 = flowRule(1, 1);
+        FlowRule r2 = flowRule(2, 2);
+        FlowRule r3 = flowRule(3, 3);
+
+        assertTrue("store should be empty",
+                   Sets.newHashSet(vnetFlowRuleService1.getFlowEntries(DID1)).isEmpty());
+        vnetFlowRuleService1.applyFlowRules(r1, r2, r3);
+        assertEquals("3 rules should exist", 3, flowCount(vnetFlowRuleService1));
+        assertTrue("Entries should be pending add.",
+                   validateState(ImmutableMap.of(
+                           r1, FlowEntry.FlowEntryState.PENDING_ADD,
+                           r2, FlowEntry.FlowEntryState.PENDING_ADD,
+                           r3, FlowEntry.FlowEntryState.PENDING_ADD)));
+    }
+
+    @Test
+    public void purgeFlowRules() {
+        FlowRule f1 = addFlowRule(1);
+        FlowRule f2 = addFlowRule(2);
+        FlowRule f3 = addFlowRule(3);
+        assertEquals("3 rules should exist", 3, flowCount(vnetFlowRuleService1));
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
+        providerService1.pushFlowMetrics(VDID1, ImmutableList.of(fe1, fe2, fe3));
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+                       RULE_ADDED, RULE_ADDED, RULE_ADDED);
+        vnetFlowRuleService1.purgeFlowRules(VDID1);
+        assertEquals("0 rule should exist", 0, flowCount(vnetFlowRuleService1));
+    }
+
+    @Test
+    public void removeFlowRules() {
+        FlowRule f1 = addFlowRule(1);
+        FlowRule f2 = addFlowRule(2);
+        FlowRule f3 = addFlowRule(3);
+        assertEquals("3 rules should exist", 3, flowCount(vnetFlowRuleService1));
+
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
+        providerService1.pushFlowMetrics(VDID1, ImmutableList.of(fe1, fe2, fe3));
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+                       RULE_ADDED, RULE_ADDED, RULE_ADDED);
+
+        vnetFlowRuleService1.removeFlowRules(f1, f2);
+        //removing from north, so no events generated
+        validateEvents(listener1, RULE_REMOVE_REQUESTED, RULE_REMOVE_REQUESTED);
+        assertEquals("3 rule should exist", 3, flowCount(vnetFlowRuleService1));
+        assertTrue("Entries should be pending remove.",
+                   validateState(ImmutableMap.of(
+                           f1, FlowEntry.FlowEntryState.PENDING_REMOVE,
+                           f2, FlowEntry.FlowEntryState.PENDING_REMOVE,
+                           f3, FlowEntry.FlowEntryState.ADDED)));
+
+        vnetFlowRuleService1.removeFlowRules(f1);
+        assertEquals("3 rule should still exist", 3, flowCount(vnetFlowRuleService1));
+    }
+
+    @Test
+    public void flowRemoved() {
+        FlowRule f1 = addFlowRule(1);
+        FlowRule f2 = addFlowRule(2);
+        StoredFlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+
+        providerService1.pushFlowMetrics(VDID1, ImmutableList.of(fe1, fe2));
+        vnetFlowRuleService1.removeFlowRules(f1);
+
+        //FIXME modification of "stored" flow entry outside of store
+        fe1.setState(FlowEntry.FlowEntryState.REMOVED);
+
+        providerService1.flowRemoved(fe1);
+
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED,
+                       RULE_ADDED, RULE_REMOVE_REQUESTED, RULE_REMOVED);
+
+        providerService1.flowRemoved(fe1);
+        validateEvents(listener1);
+
+        FlowRule f3 = flowRule(3, 3);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
+        vnetFlowRuleService1.applyFlowRules(f3);
+
+        providerService1.pushFlowMetrics(VDID1, Collections.singletonList(fe3));
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADDED, RULE_UPDATED);
+
+        providerService1.flowRemoved(fe3);
+        validateEvents(listener1);
+    }
+
+    @Test
+    public void extraneousFlow() {
+        FlowRule f1 = flowRule(1, 1);
+        FlowRule f2 = flowRule(2, 2);
+        FlowRule f3 = flowRule(3, 3);
+        vnetFlowRuleService1.applyFlowRules(f1, f2);
+
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
+
+
+        providerService1.pushFlowMetrics(VDID1, Lists.newArrayList(fe1, fe2, fe3));
+
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+                       RULE_ADDED, RULE_ADDED);
+    }
+
+    /*
+     * Tests whether a rule that was marked for removal but no flowRemoved was received
+     * is indeed removed at the next stats update.
+     */
+    @Test
+    public void flowMissingRemove() {
+        FlowRule f1 = flowRule(1, 1);
+        FlowRule f2 = flowRule(2, 2);
+        FlowRule f3 = flowRule(3, 3);
+
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        vnetFlowRuleService1.applyFlowRules(f1, f2, f3);
+
+        vnetFlowRuleService1.removeFlowRules(f3);
+
+        providerService1.pushFlowMetrics(VDID1, Lists.newArrayList(fe1, fe2));
+
+        validateEvents(listener1, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+                       RULE_REMOVE_REQUESTED, RULE_ADDED, RULE_ADDED, RULE_REMOVED);
+    }
+
+    @Test
+    public void removeByAppId() {
+        FlowRule f1 = flowRule(1, 1);
+        FlowRule f2 = flowRule(2, 2);
+        vnetFlowRuleService1.applyFlowRules(f1, f2);
+
+        vnetFlowRuleService1.removeFlowRulesById(appId);
+
+        //only check that we are in pending remove. Events and actual remove state will
+        // be set by flowRemoved call.
+        validateState(ImmutableMap.of(
+                f1, FlowEntry.FlowEntryState.PENDING_REMOVE,
+                f2, FlowEntry.FlowEntryState.PENDING_REMOVE));
+    }
+
+    //TODO:Tests for fallback
+
+    private boolean validateState(Map<FlowRule, FlowEntry.FlowEntryState> expected) {
+        Map<FlowRule, FlowEntry.FlowEntryState> expectedToCheck = new HashMap<>(expected);
+        Iterable<FlowEntry> rules = vnetFlowRuleService1.getFlowEntries(VDID1);
+        for (FlowEntry f : rules) {
+            assertTrue("Unexpected FlowRule " + f, expectedToCheck.containsKey(f));
+            assertEquals("FlowEntry" + f, expectedToCheck.get(f), f.state());
+            expectedToCheck.remove(f);
+        }
+        assertEquals(Collections.emptySet(), expectedToCheck.entrySet());
+        return true;
+    }
+
+    private class TestSelector implements TrafficSelector {
+
+        //for controlling hashcode uniqueness;
+        private final int testval;
+
+        public TestSelector(int val) {
+            testval = val;
+        }
+
+        @Override
+        public Set<Criterion> criteria() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public Criterion getCriterion(
+                org.onosproject.net.flow.criteria.Criterion.Type type) {
+            return null;
+        }
+
+        @Override
+        public int hashCode() {
+            return testval;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof TestSelector) {
+                return this.testval == ((TestSelector) o).testval;
+            }
+            return false;
+        }
+    }
+
+    private class TestTreatment implements TrafficTreatment {
+
+        //for controlling hashcode uniqueness;
+        private final int testval;
+
+        public TestTreatment(int val) {
+            testval = val;
+        }
+
+        @Override
+        public List<Instruction> deferred() {
+            return null;
+        }
+
+        @Override
+        public List<Instruction> immediate() {
+            return null;
+        }
+
+        @Override
+        public List<Instruction> allInstructions() {
+            return null;
+        }
+
+        @Override
+        public Instructions.TableTypeTransition tableTransition() {
+            return null;
+        }
+
+        @Override
+        public boolean clearedDeferred() {
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return testval;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof TestTreatment) {
+                return this.testval == ((TestTreatment) o).testval;
+            }
+            return false;
+        }
+
+        @Override
+        public Instructions.MetadataInstruction writeMetadata() {
+            return null;
+        }
+
+        @Override
+        public Instructions.StatTriggerInstruction statTrigger() {
+            return null;
+        }
+
+        @Override
+        public Instructions.MeterInstruction metered() {
+            return null;
+        }
+
+        @Override
+        public Set<Instructions.MeterInstruction> meters() {
+            return Sets.newHashSet();
+        }
+    }
+
+    private void validateEvents(TestFlowRuleListener listener, FlowRuleEvent.Type... events) {
+        if (events == null) {
+            assertTrue("events generated", listener.events.isEmpty());
+        }
+
+        int i = 0;
+        System.err.println("events :" + listener.events);
+        for (FlowRuleEvent e : listener.events) {
+            assertEquals("unexpected event", events[i], e.type());
+            i++;
+        }
+
+        assertEquals("mispredicted number of events",
+                     events.length, listener.events.size());
+
+        listener.events.clear();
+    }
+
+    private class TestFlowRuleListener implements FlowRuleListener {
+
+        public final List<FlowRuleEvent> events = new ArrayList<>();
+
+        @Override
+        public void event(FlowRuleEvent event) {
+            events.add(event);
+        }
+    }
+
+    private class TestProvider extends AbstractVirtualProvider
+            implements VirtualFlowRuleProvider {
+
+        protected TestProvider() {
+            super(new ProviderId("test", "org.onosproject.virtual.testprovider"));
+        }
+
+        @Override
+        public void applyFlowRule(NetworkId networkId, FlowRule... flowRules) {
+
+        }
+
+        @Override
+        public void removeFlowRule(NetworkId networkId, FlowRule... flowRules) {
+
+        }
+
+        @Override
+        public void executeBatch(NetworkId networkId, FlowRuleBatchOperation batch) {
+
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManagerTest.java
new file mode 100644
index 0000000..7aa0d9c
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkGroupManagerTest.java
@@ -0,0 +1,702 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Iterables;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onosproject.TestApplicationId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.GroupId;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkGroupStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.impl.provider.VirtualProviderManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualGroupProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualGroupStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.*;
+import static org.onosproject.incubator.net.virtual.impl.VirtualNetworkTestUtil.*;
+import static org.onosproject.net.NetTestTools.injectEventDispatcher;
+
+/**
+ * Test codifying the virtual group service & group provider service contracts.
+ */
+public class VirtualNetworkGroupManagerTest {
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private ServiceDirectory testDirectory;
+    private VirtualProviderManager providerRegistryService;
+
+    private EventDeliveryService eventDeliveryService;
+
+    private VirtualNetworkGroupManager groupManager1;
+    private VirtualNetworkGroupManager groupManager2;
+
+    private VirtualNetworkGroupStore groupStore;
+
+    private TestGroupProvider provider = new TestGroupProvider();
+    private VirtualGroupProviderService providerService1;
+    private VirtualGroupProviderService providerService2;
+
+    protected TestGroupListener listener1 = new TestGroupListener();
+    protected TestGroupListener listener2 = new TestGroupListener();
+
+    private VirtualNetwork vnet1;
+    private VirtualNetwork vnet2;
+
+    private ApplicationId appId;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        CoreService coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        groupStore = new SimpleVirtualGroupStore();
+
+        providerRegistryService = new VirtualProviderManager();
+        providerRegistryService.registerProvider(provider);
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        TestUtils.setField(manager, "coreService", coreService);
+
+        eventDeliveryService = new TestEventDispatcher();
+        injectEventDispatcher(manager, eventDeliveryService);
+
+        appId = new TestApplicationId("VirtualGroupManagerTest");
+
+        testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(CoreService.class, coreService)
+                .add(VirtualProviderRegistryService.class, providerRegistryService)
+                .add(EventDeliveryService.class, eventDeliveryService)
+                .add(VirtualNetworkGroupStore.class, groupStore);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+
+        vnet1 = setupVirtualNetworkTopology(manager, TID1);
+        vnet2 = setupVirtualNetworkTopology(manager, TID2);
+
+        groupManager1 = new VirtualNetworkGroupManager(manager, vnet1.id());
+        groupManager2 = new VirtualNetworkGroupManager(manager, vnet2.id());
+        groupManager1.addListener(listener1);
+        groupManager2.addListener(listener2);
+
+        providerService1 = (VirtualGroupProviderService)
+                providerRegistryService.getProviderService(vnet1.id(),
+                                                           VirtualGroupProvider.class);
+        providerService2 = (VirtualGroupProviderService)
+                providerRegistryService.getProviderService(vnet2.id(),
+                                                           VirtualGroupProvider.class);
+    }
+
+    @After
+    public void tearDown() {
+        providerRegistryService.unregisterProvider(provider);
+        assertFalse("provider should not be registered",
+                    providerRegistryService.getProviders().contains(provider.id()));
+        groupManager1.removeListener(listener1);
+        groupManager2.removeListener(listener2);
+
+        manager.deactivate();
+        virtualNetworkManagerStore.deactivate();
+    }
+
+    /**
+     * Tests group creation before the device group AUDIT completes.
+     */
+    @Test
+    public void testGroupServiceBasics() {
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID1);
+        testGroupCreationBeforeAudit(vnet2.id(), VDID1);
+    }
+
+    /**
+     * Tests initial device group AUDIT process.
+     */
+    @Test
+    public void testGroupServiceInitialAudit() {
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID1);
+        testGroupCreationBeforeAudit(vnet2.id(), VDID1);
+        // Test initial group audit process
+        testInitialAuditWithPendingGroupRequests(vnet1.id(), VDID1);
+        testInitialAuditWithPendingGroupRequests(vnet2.id(), VDID1);
+    }
+
+    /**
+     * Tests deletion process of any extraneous groups.
+     */
+    @Test
+    public void testGroupServiceAuditExtraneous() {
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID1);
+        testGroupCreationBeforeAudit(vnet2.id(), VDID1);
+
+        // Test audit with extraneous and missing groups
+        testAuditWithExtraneousMissingGroups(vnet1.id(), VDID1);
+        testAuditWithExtraneousMissingGroups(vnet2.id(), VDID1);
+    }
+
+    /**
+     * Tests re-apply process of any missing groups tests execution of
+     * any pending group creation request after the device group AUDIT completes
+     * and tests event notifications after receiving confirmation for any
+     * operations from data plane.
+     */
+    @Test
+    public void testGroupServiceAuditConfirmed() {
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID1);
+        testGroupCreationBeforeAudit(vnet2.id(), VDID1);
+
+        // Test audit with extraneous and missing groups
+        testAuditWithExtraneousMissingGroups(vnet1.id(), VDID1);
+        testAuditWithExtraneousMissingGroups(vnet2.id(), VDID1);
+
+        // Test audit with confirmed groups
+        testAuditWithConfirmedGroups(vnet1.id(), VDID1);
+        testAuditWithConfirmedGroups(vnet2.id(), VDID1);
+    }
+
+    /**
+     * Tests group Purge Operation.
+     */
+    @Test
+    public void testPurgeGroups() {
+        // Tests for virtual network 1
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID1);
+        testAuditWithExtraneousMissingGroups(vnet1.id(), VDID1);
+        // Test group add bucket operations
+        testAddBuckets(vnet1.id(), VDID1);
+        // Test group Purge operations
+        testPurgeGroupEntry(vnet1.id(), VDID1);
+
+        // Tests for virtual network 2
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet2.id(), VDID1);
+        testAuditWithExtraneousMissingGroups(vnet2.id(), VDID1);
+        // Test group add bucket operations
+        testAddBuckets(vnet2.id(), VDID1);
+        // Test group Purge operations
+        testPurgeGroupEntry(vnet2.id(), VDID1);
+    }
+
+    /**
+     * Tests group bucket modifications (additions and deletions) and
+     * Tests group deletion.
+     */
+    @Test
+    public void testGroupServiceBuckets() {
+        // Tests for virtual network 1
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID1);
+
+        testAuditWithExtraneousMissingGroups(vnet1.id(), VDID1);
+        // Test group add bucket operations
+        testAddBuckets(vnet1.id(), VDID1);
+
+        // Test group remove bucket operations
+        testRemoveBuckets(vnet1.id(), VDID1);
+
+        // Test group remove operations
+        testRemoveGroup(vnet1.id(), VDID1);
+
+        // Tests for virtual network 2
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet2.id(), VDID1);
+
+        testAuditWithExtraneousMissingGroups(vnet2.id(), VDID1);
+        // Test group add bucket operations
+        testAddBuckets(vnet2.id(), VDID1);
+
+        // Test group remove bucket operations
+        testRemoveBuckets(vnet2.id(), VDID1);
+
+        // Test group remove operations
+        testRemoveGroup(vnet2.id(), VDID1);
+    }
+
+    /**
+     * Tests group creation before the device group AUDIT completes with fallback
+     * provider.
+     */
+    @Test
+    public void testGroupServiceFallbackBasics() {
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(vnet1.id(), VDID2);
+        testGroupCreationBeforeAudit(vnet2.id(), VDID2);
+    }
+
+    // Test Group creation before AUDIT process
+    private void testGroupCreationBeforeAudit(NetworkId networkId, DeviceId deviceId) {
+        PortNumber[] ports1 = {PortNumber.portNumber(31),
+                PortNumber.portNumber(32)};
+        PortNumber[] ports2 = {PortNumber.portNumber(41),
+                PortNumber.portNumber(42)};
+        GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+        List<GroupBucket> buckets = new ArrayList<>();
+        List<PortNumber> outPorts = new ArrayList<>();
+        outPorts.addAll(Arrays.asList(ports1));
+        outPorts.addAll(Arrays.asList(ports2));
+        for (PortNumber portNumber : outPorts) {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+            tBuilder.setOutput(portNumber)
+                    .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+                    .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+                    .pushMpls()
+                    .setMpls(MplsLabel.mplsLabel(106));
+            buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                    tBuilder.build()));
+        }
+        GroupBuckets groupBuckets = new GroupBuckets(buckets);
+        GroupDescription newGroupDesc = new DefaultGroupDescription(deviceId,
+                                                                    Group.Type.SELECT,
+                                                                    groupBuckets,
+                                                                    key,
+                                                                    null,
+                                                                    appId);
+        VirtualNetworkGroupManager groupManager;
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+        } else {
+            groupManager = groupManager2;
+        }
+
+        groupManager.addGroup(newGroupDesc);
+        assertEquals(null, groupManager.getGroup(deviceId, key));
+        assertEquals(0, Iterables.size(groupManager.getGroups(deviceId, appId)));
+    }
+
+
+    // Test initial AUDIT process with pending group requests
+    private void testInitialAuditWithPendingGroupRequests(NetworkId networkId,
+                                                          DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        VirtualGroupProviderService providerService;
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+            providerService = providerService1;
+        } else {
+            groupManager = groupManager2;
+            providerService = providerService2;
+        }
+
+        PortNumber[] ports1 = {PortNumber.portNumber(31),
+                PortNumber.portNumber(32)};
+        PortNumber[] ports2 = {PortNumber.portNumber(41),
+                PortNumber.portNumber(42)};
+        GroupId gId1 = new GroupId(1);
+        Group group1 = createSouthboundGroupEntry(gId1,
+                                                  Arrays.asList(ports1),
+                                                  0, deviceId);
+        GroupId gId2 = new GroupId(2);
+        // Non zero reference count will make the group manager to queue
+        // the extraneous groups until reference count is zero.
+        Group group2 = createSouthboundGroupEntry(gId2,
+                                                  Arrays.asList(ports2),
+                                                  2, deviceId);
+        List<Group> groupEntries = Arrays.asList(group1, group2);
+        providerService.pushGroupMetrics(deviceId, groupEntries);
+        // First group metrics would trigger the device audit completion
+        // post which all pending group requests are also executed.
+        GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+        Group createdGroup = groupManager.getGroup(deviceId, key);
+        int createdGroupId = createdGroup.id().id();
+        assertNotEquals(gId1.id().intValue(), createdGroupId);
+        assertNotEquals(gId2.id().intValue(), createdGroupId);
+
+        List<GroupOperation> expectedGroupOps = Arrays.asList(
+                GroupOperation.createDeleteGroupOperation(gId1,
+                                                          Group.Type.SELECT),
+                GroupOperation.createAddGroupOperation(
+                        createdGroup.id(),
+                        Group.Type.SELECT,
+                        createdGroup.buckets()));
+        if (deviceId.equals(VDID1)) {
+            provider.validate(networkId, deviceId, expectedGroupOps);
+        }
+    }
+
+    // Test AUDIT process with extraneous groups and missing groups
+    private void testAuditWithExtraneousMissingGroups(NetworkId networkId,
+                                                      DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        VirtualGroupProviderService providerService;
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+            providerService = providerService1;
+        } else {
+            groupManager = groupManager2;
+            providerService = providerService2;
+        }
+
+        PortNumber[] ports1 = {PortNumber.portNumber(31),
+                PortNumber.portNumber(32)};
+        PortNumber[] ports2 = {PortNumber.portNumber(41),
+                PortNumber.portNumber(42)};
+        GroupId gId1 = new GroupId(1);
+        Group group1 = createSouthboundGroupEntry(gId1,
+                                                  Arrays.asList(ports1),
+                                                  0, deviceId);
+        GroupId gId2 = new GroupId(2);
+        Group group2 = createSouthboundGroupEntry(gId2,
+                                                  Arrays.asList(ports2),
+                                                  0, deviceId);
+        List<Group> groupEntries = Arrays.asList(group1, group2);
+        providerService.pushGroupMetrics(deviceId, groupEntries);
+        GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+        Group createdGroup = groupManager.getGroup(deviceId, key);
+        List<GroupOperation> expectedGroupOps = Arrays.asList(
+                GroupOperation.createDeleteGroupOperation(gId1,
+                                                          Group.Type.SELECT),
+                GroupOperation.createDeleteGroupOperation(gId2,
+                                                          Group.Type.SELECT),
+                GroupOperation.createAddGroupOperation(createdGroup.id(),
+                                                       Group.Type.SELECT,
+                                                       createdGroup.buckets()));
+        if (deviceId.equals(VDID1)) {
+            provider.validate(networkId, deviceId, expectedGroupOps);
+        }
+    }
+
+    // Test AUDIT with confirmed groups
+    private void testAuditWithConfirmedGroups(NetworkId networkId,
+                                              DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        VirtualGroupProviderService providerService;
+        TestGroupListener listener;
+
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+            providerService = providerService1;
+            listener = listener1;
+        } else {
+            groupManager = groupManager2;
+            providerService = providerService2;
+            listener = listener2;
+        }
+
+        GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
+        Group createdGroup = groupManager.getGroup(deviceId, key);
+        createdGroup = new DefaultGroup(createdGroup.id(),
+                                        deviceId,
+                                        Group.Type.SELECT,
+                                        createdGroup.buckets());
+        List<Group> groupEntries = Collections.singletonList(createdGroup);
+        providerService.pushGroupMetrics(deviceId, groupEntries);
+        listener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_ADDED));
+    }
+
+    private Group createSouthboundGroupEntry(GroupId gId,
+                                             List<PortNumber> ports,
+                                             long referenceCount, DeviceId deviceId) {
+        List<PortNumber> outPorts = new ArrayList<>();
+        outPorts.addAll(ports);
+
+        List<GroupBucket> buckets = new ArrayList<>();
+        for (PortNumber portNumber : outPorts) {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+            tBuilder.setOutput(portNumber)
+                    .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+                    .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+                    .pushMpls()
+                    .setMpls(MplsLabel.mplsLabel(106));
+            buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                    tBuilder.build()));
+        }
+        GroupBuckets groupBuckets = new GroupBuckets(buckets);
+        StoredGroupEntry group = new DefaultGroup(
+                gId, deviceId, Group.Type.SELECT, groupBuckets);
+        group.setReferenceCount(referenceCount);
+        return group;
+    }
+
+    // Test group add bucket operations
+    private void testAddBuckets(NetworkId networkId, DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        VirtualGroupProviderService providerService;
+        TestGroupListener listener;
+
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+            providerService = providerService1;
+            listener = listener1;
+        } else {
+            groupManager = groupManager2;
+            providerService = providerService2;
+            listener = listener2;
+        }
+
+        GroupKey addKey = new DefaultGroupKey("group1AddBuckets".getBytes());
+
+        GroupKey prevKey = new DefaultGroupKey("group1BeforeAudit".getBytes());
+        Group createdGroup = groupManager.getGroup(deviceId, prevKey);
+        List<GroupBucket> buckets = new ArrayList<>();
+        buckets.addAll(createdGroup.buckets().buckets());
+
+        PortNumber[] addPorts = {PortNumber.portNumber(51),
+                PortNumber.portNumber(52)};
+        List<PortNumber> outPorts;
+        outPorts = new ArrayList<>();
+        outPorts.addAll(Arrays.asList(addPorts));
+        List<GroupBucket> addBuckets;
+        addBuckets = new ArrayList<>();
+        for (PortNumber portNumber : outPorts) {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+            tBuilder.setOutput(portNumber)
+                    .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+                    .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+                    .pushMpls()
+                    .setMpls(MplsLabel.mplsLabel(106));
+            addBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                    tBuilder.build()));
+            buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                    tBuilder.build()));
+        }
+        GroupBuckets groupAddBuckets = new GroupBuckets(addBuckets);
+        groupManager.addBucketsToGroup(deviceId,
+                                       prevKey,
+                                       groupAddBuckets,
+                                       addKey,
+                                       appId);
+        GroupBuckets updatedBuckets = new GroupBuckets(buckets);
+        List<GroupOperation> expectedGroupOps = Collections.singletonList(
+                GroupOperation.createModifyGroupOperation(createdGroup.id(),
+                                                          Group.Type.SELECT,
+                                                          updatedBuckets));
+        if (deviceId.equals(VDID1)) {
+            provider.validate(networkId, deviceId, expectedGroupOps);
+        }
+
+        Group existingGroup = groupManager.getGroup(deviceId, addKey);
+        List<Group> groupEntries = Collections.singletonList(existingGroup);
+        providerService.pushGroupMetrics(deviceId, groupEntries);
+        listener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATED));
+    }
+
+    // Test purge group entry operations
+    private void testPurgeGroupEntry(NetworkId networkId, DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+        } else {
+            groupManager = groupManager2;
+        }
+
+        assertEquals(1, Iterables.size(groupManager.getGroups(deviceId, appId)));
+        groupManager.purgeGroupEntries(deviceId);
+        assertEquals(0, Iterables.size(groupManager.getGroups(deviceId, appId)));
+    }
+
+    // Test group remove bucket operations
+    private void testRemoveBuckets(NetworkId networkId, DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        VirtualGroupProviderService providerService;
+        TestGroupListener listener;
+
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+            providerService = providerService1;
+            listener = listener1;
+        } else {
+            groupManager = groupManager2;
+            providerService = providerService2;
+            listener = listener2;
+        }
+
+        GroupKey removeKey = new DefaultGroupKey("group1RemoveBuckets".getBytes());
+
+        GroupKey prevKey = new DefaultGroupKey("group1AddBuckets".getBytes());
+        Group createdGroup = groupManager.getGroup(deviceId, prevKey);
+        List<GroupBucket> buckets = new ArrayList<>();
+        buckets.addAll(createdGroup.buckets().buckets());
+
+        PortNumber[] removePorts = {PortNumber.portNumber(31),
+                PortNumber.portNumber(32)};
+        List<PortNumber> outPorts = new ArrayList<>();
+        outPorts.addAll(Arrays.asList(removePorts));
+        List<GroupBucket> removeBuckets = new ArrayList<>();
+        for (PortNumber portNumber : outPorts) {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+            tBuilder.setOutput(portNumber)
+                    .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+                    .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+                    .pushMpls()
+                    .setMpls(MplsLabel.mplsLabel(106));
+            removeBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                    tBuilder.build()));
+            buckets.remove(DefaultGroupBucket.createSelectGroupBucket(
+                    tBuilder.build()));
+        }
+        GroupBuckets groupRemoveBuckets = new GroupBuckets(removeBuckets);
+        groupManager.removeBucketsFromGroup(deviceId,
+                                            prevKey,
+                                            groupRemoveBuckets,
+                                            removeKey,
+                                            appId);
+        GroupBuckets updatedBuckets = new GroupBuckets(buckets);
+        List<GroupOperation> expectedGroupOps = Collections.singletonList(
+                GroupOperation.createModifyGroupOperation(createdGroup.id(),
+                                                          Group.Type.SELECT,
+                                                          updatedBuckets));
+        if (deviceId.equals(VDID1)) {
+            provider.validate(networkId, deviceId, expectedGroupOps);
+        }
+
+        Group existingGroup = groupManager.getGroup(deviceId, removeKey);
+        List<Group> groupEntries = Collections.singletonList(existingGroup);
+        providerService.pushGroupMetrics(deviceId, groupEntries);
+        listener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_UPDATED));
+    }
+
+    // Test group remove operations
+    private void testRemoveGroup(NetworkId networkId, DeviceId deviceId) {
+        VirtualNetworkGroupManager groupManager;
+        VirtualGroupProviderService providerService;
+        TestGroupListener listener;
+
+        if (networkId.id() == 1) {
+            groupManager = groupManager1;
+            providerService = providerService1;
+            listener = listener1;
+        } else {
+            groupManager = groupManager2;
+            providerService = providerService2;
+            listener = listener2;
+        }
+
+        GroupKey currKey = new DefaultGroupKey("group1RemoveBuckets".getBytes());
+        Group existingGroup = groupManager.getGroup(deviceId, currKey);
+        groupManager.removeGroup(deviceId, currKey, appId);
+        List<GroupOperation> expectedGroupOps = Collections.singletonList(
+                GroupOperation.createDeleteGroupOperation(existingGroup.id(),
+                                                          Group.Type.SELECT));
+        if (deviceId.equals(VDID1)) {
+            provider.validate(networkId, deviceId, expectedGroupOps);
+        }
+
+        List<Group> groupEntries = Collections.emptyList();
+        providerService.pushGroupMetrics(deviceId, groupEntries);
+        listener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_REMOVED));
+    }
+
+    private class TestGroupProvider extends AbstractVirtualProvider
+            implements VirtualGroupProvider {
+        NetworkId lastNetworkId;
+        DeviceId lastDeviceId;
+        List<GroupOperation> groupOperations = new ArrayList<>();
+
+        protected TestGroupProvider() {
+            super(new ProviderId("test", "org.onosproject.virtual.testprovider"));
+        }
+
+        @Override
+        public void performGroupOperation(NetworkId networkId, DeviceId deviceId,
+                                          GroupOperations groupOps) {
+            lastNetworkId = networkId;
+            lastDeviceId = deviceId;
+            groupOperations.addAll(groupOps.operations());
+        }
+
+        public void validate(NetworkId expectedNetworkId, DeviceId expectedDeviceId,
+                             List<GroupOperation> expectedGroupOps) {
+            if (expectedGroupOps == null) {
+                assertTrue("events generated", groupOperations.isEmpty());
+                return;
+            }
+
+            assertEquals(lastNetworkId, expectedNetworkId);
+            assertEquals(lastDeviceId, expectedDeviceId);
+            assertTrue((this.groupOperations.containsAll(expectedGroupOps) &&
+                    expectedGroupOps.containsAll(groupOperations)));
+
+            groupOperations.clear();
+            lastDeviceId = null;
+            lastNetworkId = null;
+        }
+    }
+
+    private static class TestGroupListener implements GroupListener {
+        final List<GroupEvent> events = new ArrayList<>();
+
+        @Override
+        public void event(GroupEvent event) {
+            events.add(event);
+        }
+
+        public void validateEvent(List<GroupEvent.Type> expectedEvents) {
+            int i = 0;
+            System.err.println("events :" + events);
+            for (GroupEvent e : events) {
+                assertEquals("unexpected event", expectedEvents.get(i), e.type());
+                i++;
+            }
+            assertEquals("mispredicted number of events",
+                         expectedEvents.size(), events.size());
+            events.clear();
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManagerTest.java
new file mode 100644
index 0000000..e60d343
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkHostManagerTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Iterators;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.TestDeviceParams;
+import org.onosproject.net.host.HostService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import static org.junit.Assert.*;
+
+/**
+ * Junit tests for VirtualNetworkHostService.
+ */
+public class VirtualNetworkHostManagerTest extends TestDeviceParams {
+    private final String tenantIdValue1 = "TENANT_ID1";
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private TestServiceDirectory testDirectory;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        CoreService coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+    }
+
+    /**
+     * Sets up a virtual network with hosts.
+     *
+     * @return virtual network
+     */
+    private VirtualNetwork setupVnet() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        ConnectPoint hostCp1 = new ConnectPoint(DID1, P1);
+        ConnectPoint hostCp2 = new ConnectPoint(DID2, P2);
+        manager.createVirtualPort(virtualNetwork.id(), hostCp1.deviceId(), hostCp1.port(),
+                new ConnectPoint(virtualDevice1.id(), hostCp1.port()));
+        manager.createVirtualPort(virtualNetwork.id(), hostCp2.deviceId(), hostCp2.port(),
+                new ConnectPoint(virtualDevice2.id(), hostCp2.port()));
+
+        manager.createVirtualHost(virtualNetwork.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
+        manager.createVirtualHost(virtualNetwork.id(), HID2, MAC2, VLAN2, LOC2, IPSET2);
+        return virtualNetwork;
+    }
+
+    /**
+     * Sets up a virtual network with no hosts.
+     *
+     * @return virtual network
+     */
+    private VirtualNetwork setupEmptyVnet() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        ConnectPoint hostCp1 = new ConnectPoint(DID1, P1);
+        ConnectPoint hostCp2 = new ConnectPoint(DID2, P2);
+        manager.createVirtualPort(virtualNetwork.id(), hostCp1.deviceId(), hostCp1.port(),
+                new ConnectPoint(virtualDevice1.id(), hostCp1.port()));
+        manager.createVirtualPort(virtualNetwork.id(), hostCp2.deviceId(), hostCp2.port(),
+                new ConnectPoint(virtualDevice2.id(), hostCp2.port()));
+
+        return virtualNetwork;
+    }
+
+    /**
+     * Tests the getHosts(), getHost(), getHostsByXX(), getConnectedHosts() methods
+     * on a non-empty virtual network.
+     */
+    @Test
+    public void testGetHostsOnNonEmptyVnet() {
+        VirtualNetwork virtualNetwork = setupEmptyVnet();
+        VirtualHost vhost1 = manager.createVirtualHost(virtualNetwork.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
+        VirtualHost vhost2 = manager.createVirtualHost(virtualNetwork.id(), HID2, MAC2, VLAN2, LOC2, IPSET2);
+        HostService hostService = manager.get(virtualNetwork.id(), HostService.class);
+
+        // test the getHosts() and getHostCount() methods
+        Iterator<Host> itHosts = hostService.getHosts().iterator();
+        assertEquals("The host set size did not match.", 2, Iterators.size(itHosts));
+        assertEquals("The host count did not match.", 2, hostService.getHostCount());
+
+        // test the getHost() method
+        Host testHost = hostService.getHost(HID2);
+        assertEquals("The expected host did not match.", vhost2, testHost);
+
+        // test the getHostsByVlan(...) method
+        Collection<Host> collHost = hostService.getHostsByVlan(VLAN1);
+        assertEquals("The host set size did not match.", 1, collHost.size());
+        assertTrue("The host did not match.", collHost.contains(vhost1));
+
+        // test the getHostsByMac(...) method
+        collHost = hostService.getHostsByMac(MAC2);
+        assertEquals("The host set size did not match.", 1, collHost.size());
+        assertTrue("The host did not match.", collHost.contains(vhost2));
+
+        // test the getHostsByIp(...) method
+        collHost = hostService.getHostsByIp(IP1);
+        assertEquals("The host set size did not match.", 2, collHost.size());
+        collHost = hostService.getHostsByIp(IP2);
+        assertEquals("The host set size did not match.", 1, collHost.size());
+        assertTrue("The host did not match.", collHost.contains(vhost1));
+
+        // test the getConnectedHosts(ConnectPoint) method
+        collHost = hostService.getConnectedHosts(LOC1);
+        assertEquals("The host set size did not match.", 1, collHost.size());
+        assertTrue("The host did not match.", collHost.contains(vhost1));
+
+        // test the getConnectedHosts(DeviceId) method
+        collHost = hostService.getConnectedHosts(DID2);
+        assertEquals("The host set size did not match.", 1, collHost.size());
+        assertTrue("The host did not match.", collHost.contains(vhost2));
+    }
+
+    /**
+     * Tests the getHosts(), getHost(), getHostsByXX(), getConnectedHosts() methods
+     * on an empty virtual network.
+     */
+    @Test
+    public void testGetHostsOnEmptyVnet() {
+        VirtualNetwork virtualNetwork = setupEmptyVnet();
+        HostService hostService = manager.get(virtualNetwork.id(), HostService.class);
+
+        // test the getHosts() and getHostCount() methods
+        Iterator<Host> itHosts = hostService.getHosts().iterator();
+        assertEquals("The host set size did not match.", 0, Iterators.size(itHosts));
+        assertEquals("The host count did not match.", 0, hostService.getHostCount());
+
+        // test the getHost() method
+        Host testHost = hostService.getHost(HID2);
+        assertNull("The host should be null.", testHost);
+
+        // test the getHostsByVlan(...) method
+        Collection<Host> collHost = hostService.getHostsByVlan(VLAN1);
+        assertEquals("The host set size did not match.", 0, collHost.size());
+
+        // test the getHostsByMac(...) method
+        collHost = hostService.getHostsByMac(MAC2);
+        assertEquals("The host set size did not match.", 0, collHost.size());
+
+        // test the getHostsByIp(...) method
+        collHost = hostService.getHostsByIp(IP1);
+        assertEquals("The host set size did not match.", 0, collHost.size());
+
+        // test the getConnectedHosts(ConnectPoint) method
+        collHost = hostService.getConnectedHosts(LOC1);
+        assertEquals("The host set size did not match.", 0, collHost.size());
+
+        // test the getConnectedHosts(DeviceId) method
+        collHost = hostService.getConnectedHosts(DID2);
+        assertEquals("The host set size did not match.", 0, collHost.size());
+    }
+
+    /**
+     * Tests querying for a host using a null host identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetHostByNullId() {
+        VirtualNetwork vnet = setupEmptyVnet();
+        HostService hostService = manager.get(vnet.id(), HostService.class);
+
+        hostService.getHost(null);
+    }
+
+    /**
+     * Tests querying for hosts with null mac.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetHostsByNullMac() {
+        VirtualNetwork vnet = setupEmptyVnet();
+        HostService hostService = manager.get(vnet.id(), HostService.class);
+
+        hostService.getHostsByMac(null);
+    }
+
+    /**
+     * Tests querying for hosts with null vlan.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetHostsByNullVlan() {
+        VirtualNetwork vnet = setupEmptyVnet();
+        HostService hostService = manager.get(vnet.id(), HostService.class);
+
+        hostService.getHostsByVlan(null);
+    }
+
+    /**
+     * Tests querying for hosts with null ip.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetHostsByNullIp() {
+        VirtualNetwork vnet = setupVnet();
+        HostService hostService = manager.get(vnet.id(), HostService.class);
+
+        hostService.getHostsByIp(null);
+    }
+
+    /**
+     * Tests querying for connected hosts with null host location (connect point).
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetConnectedHostsByNullLoc() {
+        VirtualNetwork vnet = setupEmptyVnet();
+        HostService hostService = manager.get(vnet.id(), HostService.class);
+
+        hostService.getConnectedHosts((ConnectPoint) null);
+    }
+
+    /**
+     * Tests querying for connected hosts with null device id.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetConnectedHostsByNullDeviceId() {
+        VirtualNetwork vnet = setupVnet();
+        HostService hostService = manager.get(vnet.id(), HostService.class);
+
+        hostService.getConnectedHosts((DeviceId) null);
+    }
+
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManagerTest.java
new file mode 100644
index 0000000..ba0adda
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentManagerTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.TestApplicationId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.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.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualIntentStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.FakeIntentManager;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentCompiler;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.IntentTestsMocks;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.intent.PathIntent;
+import org.onosproject.net.intent.TestableIntentService;
+import org.onosproject.net.intent.WorkPartitionService;
+import org.onosproject.net.intent.WorkPartitionServiceAdapter;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Junit tests for VirtualNetworkIntentService.
+ */
+@Ignore("deprecated prototype implementation")
+public class VirtualNetworkIntentManagerTest extends TestDeviceParams {
+
+    private final String tenantIdValue1 = "TENANT_ID1";
+    private static final ApplicationId APP_ID =
+            new TestApplicationId("MyAppId");
+
+    private ConnectPoint cp1;
+    private ConnectPoint cp2;
+    private ConnectPoint cp3;
+    private ConnectPoint cp4;
+    private ConnectPoint cp5;
+    private ConnectPoint cp6;
+    private VirtualLink link1;
+    private VirtualLink link2;
+    private VirtualLink link3;
+    private VirtualLink link4;
+    private VirtualLink link5;
+    private VirtualLink link6;
+
+    private VirtualNetworkManager manager;
+    private static DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private VirtualNetworkIntentStore intentStore;
+    private CoreService coreService;
+    private TestableIntentService intentService = new FakeIntentManager();
+    private VirtualNetworkIntentManager vnetIntentService;
+    private TestIntentCompiler compiler = new TestIntentCompiler();
+    private IntentExtensionService intentExtensionService;
+    private WorkPartitionService workPartitionService;
+    private ServiceDirectory testDirectory;
+    private TestListener listener = new TestListener();
+    private static final int MAX_WAIT_TIME = 5;
+    private static final int MAX_PERMITS = 1;
+    private static Semaphore created;
+    private static Semaphore withdrawn;
+    private static Semaphore purged;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+        intentStore = new SimpleVirtualIntentStore();
+
+        coreService = new VirtualNetworkIntentManagerTest.TestCoreService();
+
+        MockIdGenerator.cleanBind();
+
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+        intentService.addListener(listener);
+
+        // Register a compiler and an installer both setup for success.
+        intentExtensionService = intentService;
+        intentExtensionService.registerCompiler(VirtualNetworkIntent.class, compiler);
+
+        created = new Semaphore(0, true);
+        withdrawn = new Semaphore(0, true);
+        purged = new Semaphore(0, true);
+
+        workPartitionService = new WorkPartitionServiceAdapter();
+        testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(IntentService.class, intentService)
+                .add(WorkPartitionService.class, workPartitionService);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+        MockIdGenerator.unbind();
+        intentService.removeListener(listener);
+        created = null;
+        withdrawn = null;
+        purged = null;
+    }
+
+    /**
+     * Method to create the virtual network for further testing.
+     *
+     * @return virtual network
+     */
+    private VirtualNetwork setupVirtualNetworkTopology() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        VirtualDevice virtualDevice3 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID3);
+        VirtualDevice virtualDevice4 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID4);
+
+        Port port1 = new DefaultPort(virtualDevice1, PortNumber.portNumber(1), true);
+        cp1 = new ConnectPoint(virtualDevice1.id(), port1.number());
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice1.id(), port1.number(), cp1);
+
+        Port port2 = new DefaultPort(virtualDevice1, PortNumber.portNumber(2), true);
+        cp2 = new ConnectPoint(virtualDevice1.id(), port2.number());
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice1.id(), port2.number(), cp2);
+
+        Port port3 = new DefaultPort(virtualDevice2, PortNumber.portNumber(3), true);
+        cp3 = new ConnectPoint(virtualDevice2.id(), port3.number());
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice2.id(), port3.number(), cp3);
+
+        Port port4 = new DefaultPort(virtualDevice2, PortNumber.portNumber(4), true);
+        cp4 = new ConnectPoint(virtualDevice2.id(), port4.number());
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice2.id(), port4.number(), cp4);
+
+        Port port5 = new DefaultPort(virtualDevice3, PortNumber.portNumber(5), true);
+        cp5 = new ConnectPoint(virtualDevice3.id(), port5.number());
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice3.id(), port5.number(), cp5);
+
+        Port port6 = new DefaultPort(virtualDevice3, PortNumber.portNumber(6), true);
+        cp6 = new ConnectPoint(virtualDevice3.id(), port6.number());
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice3.id(), port6.number(), cp6);
+
+        link1 = manager.createVirtualLink(virtualNetwork.id(), cp1, cp3);
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        link2 = manager.createVirtualLink(virtualNetwork.id(), cp3, cp1);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+        link3 = manager.createVirtualLink(virtualNetwork.id(), cp4, cp5);
+        virtualNetworkManagerStore.updateLink(link3, link3.tunnelId(), Link.State.ACTIVE);
+        link4 = manager.createVirtualLink(virtualNetwork.id(), cp5, cp4);
+        virtualNetworkManagerStore.updateLink(link4, link4.tunnelId(), Link.State.ACTIVE);
+
+        vnetIntentService = new VirtualNetworkIntentManager(manager, virtualNetwork.id());
+        vnetIntentService.intentStore = intentStore;
+        return virtualNetwork;
+    }
+
+    /**
+     * Tests the submit(), withdraw(), and purge() methods.
+     */
+    @Test
+    public void testCreateAndRemoveIntent() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        Key intentKey = Key.of("test", APP_ID);
+
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new EncapsulationConstraint(EncapsulationType.VLAN));
+
+        VirtualNetworkIntent virtualIntent = VirtualNetworkIntent.builder()
+                .networkId(virtualNetwork.id())
+                .key(intentKey)
+                .appId(APP_ID)
+                .ingressPoint(cp1)
+                .egressPoint(cp5)
+                .constraints(constraints)
+                .build();
+        // Test the submit() method.
+        vnetIntentService.submit(virtualIntent);
+
+        // Wait for the both intents to go into an INSTALLED state.
+        try {
+            if (!created.tryAcquire(MAX_PERMITS, MAX_WAIT_TIME, TimeUnit.SECONDS)) {
+                fail("Failed to wait for intent to get installed.");
+            }
+        } catch (InterruptedException e) {
+            fail("Semaphore exception during intent installation." + e.getMessage());
+        }
+
+        // Test the getIntentState() method
+        assertEquals("The intent state did not match as expected.", IntentState.INSTALLED,
+                     vnetIntentService.getIntentState(virtualIntent.key()));
+
+        // Test the withdraw() method.
+        vnetIntentService.withdraw(virtualIntent);
+        // Wait for the both intents to go into a WITHDRAWN state.
+        try {
+            if (!withdrawn.tryAcquire(MAX_PERMITS, MAX_WAIT_TIME, TimeUnit.SECONDS)) {
+                fail("Failed to wait for intent to get withdrawn.");
+            }
+        } catch (InterruptedException e) {
+            fail("Semaphore exception during intent withdrawal." + e.getMessage());
+        }
+
+        // Test the getIntentState() method
+        assertEquals("The intent state did not match as expected.", IntentState.WITHDRAWN,
+                     vnetIntentService.getIntentState(virtualIntent.key()));
+
+        // Test the purge() method.
+        vnetIntentService.purge(virtualIntent);
+        // Wait for the both intents to be removed/purged.
+        try {
+            if (!purged.tryAcquire(MAX_PERMITS, MAX_WAIT_TIME, TimeUnit.SECONDS)) {
+                fail("Failed to wait for intent to get purged.");
+            }
+        } catch (InterruptedException e) {
+            fail("Semaphore exception during intent purging." + e.getMessage());
+        }
+
+    }
+
+    /**
+     * Tests the getIntents, getIntent(), getIntentData(), getIntentCount(),
+     * isLocal() methods.
+     */
+    @Test
+    public void testGetIntents() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        Key intentKey = Key.of("test", APP_ID);
+
+        List<Constraint> constraints = new ArrayList<>();
+        constraints.add(new EncapsulationConstraint(EncapsulationType.VLAN));
+
+        VirtualNetworkIntent virtualIntent = VirtualNetworkIntent.builder()
+                .networkId(virtualNetwork.id())
+                .key(intentKey)
+                .appId(APP_ID)
+                .ingressPoint(cp1)
+                .egressPoint(cp5)
+                .constraints(constraints)
+                .build();
+        // Test the submit() method.
+        vnetIntentService.submit(virtualIntent);
+
+        // Wait for the both intents to go into an INSTALLED state.
+        try {
+            if (!created.tryAcquire(MAX_PERMITS, MAX_WAIT_TIME, TimeUnit.SECONDS)) {
+                fail("Failed to wait for intent to get installed.");
+            }
+        } catch (InterruptedException e) {
+            fail("Semaphore exception during intent installation." + e.getMessage());
+        }
+
+        // Test the getIntents() method
+        assertEquals("The intents size did not match as expected.", 1,
+                     Iterators.size(vnetIntentService.getIntents().iterator()));
+
+        // Test the getIntent() method
+        assertNotNull("The intent should have been found.", vnetIntentService.getIntent(virtualIntent.key()));
+
+        // Test the getIntentData() method
+        assertEquals("The intent data size did not match as expected.", 1,
+                     Iterators.size(vnetIntentService.getIntentData().iterator()));
+
+        // Test the getIntentCount() method
+        assertEquals("The intent count did not match as expected.", 1,
+                     vnetIntentService.getIntentCount());
+
+        // Test the isLocal() method
+        assertTrue("The intent should be local.", vnetIntentService.isLocal(virtualIntent.key()));
+
+    }
+
+    /**
+     * Test listener to listen for intent events.
+     */
+    private static class TestListener implements IntentListener {
+
+        @Override
+        public void event(IntentEvent event) {
+            switch (event.type()) {
+                case INSTALLED:
+                    // Release one permit on the created semaphore since the Intent event was received.
+//                    virtualNetworkManagerStore.addOrUpdateIntent(event.subject(), IntentState.INSTALLED);
+                    created.release();
+                    break;
+                case WITHDRAWN:
+                    // Release one permit on the removed semaphore since the Intent event was received.
+//                    virtualNetworkManagerStore.addOrUpdateIntent(event.subject(), IntentState.WITHDRAWN);
+                    withdrawn.release();
+                    break;
+                case PURGED:
+                    // Release one permit on the purged semaphore since the Intent event was received.
+                    purged.release();
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+    }
+
+    private static class TestIntentCompiler implements IntentCompiler<VirtualNetworkIntent> {
+        @Override
+        public List<Intent> compile(VirtualNetworkIntent intent, List<Intent> installable) {
+            return Lists.newArrayList(new MockInstallableIntent());
+        }
+    }
+
+    private static class MockInstallableIntent extends FlowRuleIntent {
+
+        public MockInstallableIntent() {
+            super(APP_ID, null, Collections.singletonList(new IntentTestsMocks.MockFlowRule(100)),
+                    Collections.emptyList(), PathIntent.ProtectionType.PRIMARY, null);
+        }
+    }
+
+//    private void addOrUpdateIntent(Intent intent, IntentState state) {
+//        checkNotNull(intent, "Intent cannot be null");
+//        IntentData intentData = intentStore.(intent.key());
+//        if (intentData == null) {
+//            intentData = new IntentData(intent, state, new WallClockTimestamp(System.currentTimeMillis()));
+//        } else {
+//            intentData = new IntentData(intent, state, intentData.version());
+//        }
+//        intentKeyIntentDataMap.put(intent.key(), intentData);
+//    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManagerTest.java
new file mode 100644
index 0000000..2805f9e
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkLinkManagerTest.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Iterators;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.net.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.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * Junit tests for VirtualNetworkLinkService.
+ */
+public class VirtualNetworkLinkManagerTest extends TestDeviceParams {
+
+    private final String tenantIdValue1 = "TENANT_ID1";
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private CoreService coreService;
+    private TestServiceDirectory testDirectory;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        coreService = new VirtualNetworkLinkManagerTest.TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+    }
+
+    /**
+     * Tests the getLinks(), getActiveLinks(), getLinkCount(), getLink(),
+     * getLinks(ConnectPoint), getDeviceLinks(), getDeviceEgressLinks(), getDeviceIngressLinks(),
+     * getEgressLinks(), getIngressLinks() methods.
+     */
+    @Test
+    public void testGetLinks() {
+
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), src.deviceId(), src.port(),
+                                  new ConnectPoint(srcVirtualDevice.id(), src.port()));
+
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), dst.deviceId(), dst.port(),
+                                  new ConnectPoint(dstVirtualDevice.id(), dst.port()));
+
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), src, dst);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), dst, src);
+
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getLinks() method
+        Iterator<Link> it = linkService.getLinks().iterator();
+        assertEquals("The link set size did not match.", 2, Iterators.size(it));
+
+        // test the getActiveLinks() method where all links are INACTIVE
+        Iterator<Link> it2 = linkService.getActiveLinks().iterator();
+        assertEquals("The link set size did not match.", 0, Iterators.size(it2));
+
+        // test the getActiveLinks() method where one link is ACTIVE
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        Iterator<Link> it3 = linkService.getActiveLinks().iterator();
+        assertEquals("The link set size did not match.", 1, Iterators.size(it3));
+
+        // test the getLinkCount() method
+        assertEquals("The link set size did not match.", 2, linkService.getLinkCount());
+
+        // test the getLink() method
+        assertEquals("The expect link did not match.", link1,
+                     linkService.getLink(src, dst));
+        assertEquals("The expect link did not match.", link2,
+                     linkService.getLink(dst, src));
+        assertNotEquals("The expect link should not have matched.", link1,
+                        linkService.getLink(dst, src));
+
+        // test the getLinks(ConnectPoint) method
+        assertEquals("The link set size did not match.", 2, linkService.getLinks(src).size());
+        assertEquals("The link set size did not match.", 2, linkService.getLinks(dst).size());
+        ConnectPoint connectPoint = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(3));
+        assertEquals("The link set size did not match.", 0, linkService.getLinks(connectPoint).size());
+
+        // test the getDeviceLinks() method
+        assertEquals("The link set size did not match.", 2,
+                     linkService.getDeviceLinks(DID1).size());
+        assertEquals("The link set size did not match.", 2,
+                     linkService.getDeviceLinks(DID2).size());
+        assertEquals("The link set size did not match.", 0,
+                     linkService.getDeviceLinks(DID3).size());
+
+        // test the getDeviceEgressLinks() method
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getDeviceEgressLinks(DID1).size());
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getDeviceEgressLinks(DID2).size());
+        assertEquals("The link set size did not match.", 0,
+                     linkService.getDeviceEgressLinks(DID3).size());
+
+        // test the getDeviceIngressLinks() method
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getDeviceIngressLinks(DID1).size());
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getDeviceIngressLinks(DID2).size());
+        assertEquals("The link set size did not match.", 0,
+                     linkService.getDeviceIngressLinks(DID3).size());
+
+        // test the getEgressLinks() method
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getEgressLinks(src).size());
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getEgressLinks(dst).size());
+        assertEquals("The link set size did not match.", 0,
+                     linkService.getEgressLinks(connectPoint).size());
+
+        // test the getIngressLinks() method
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getIngressLinks(src).size());
+        assertEquals("The link set size did not match.", 1,
+                     linkService.getIngressLinks(dst).size());
+        assertEquals("The link set size did not match.", 0,
+                     linkService.getIngressLinks(connectPoint).size());
+    }
+
+    /**
+     * Tests the getLink() method using a null src connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetLinkByNullSrc() {
+
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualLink(virtualNetwork.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork.id(), dst, src);
+
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getLink() method with a null src connect point.
+        linkService.getLink(null, dst);
+    }
+
+    /**
+     * Tests the getLink() method using a null dst connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetLinkByNullDst() {
+
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualLink(virtualNetwork.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork.id(), dst, src);
+
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getLink() method with a null dst connect point.
+        linkService.getLink(src, null);
+    }
+
+    /**
+     * Tests querying for links using a null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDeviceLinksByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getDeviceLinks() method with a null device identifier.
+        linkService.getDeviceLinks(null);
+    }
+
+    /**
+     * Tests querying for links using a null connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetLinksByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getLinks() method with a null connect point.
+        linkService.getLinks(null);
+    }
+
+    /**
+     * Tests querying for device egress links using a null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDeviceEgressLinksByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getDeviceEgressLinks() method with a null device identifier.
+        linkService.getDeviceEgressLinks(null);
+    }
+
+    /**
+     * Tests querying for device ingress links using a null device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDeviceIngressLinksByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getDeviceIngressLinks() method with a null device identifier.
+        linkService.getDeviceIngressLinks(null);
+    }
+
+    /**
+     * Tests querying for egress links using a null connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetEgressLinksByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getEgressLinks() method with a null connect point.
+        linkService.getEgressLinks(null);
+    }
+
+    /**
+     * Tests querying for ingress links using a null connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetIngressLinksByNullId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        LinkService linkService = manager.get(virtualNetwork.id(), LinkService.class);
+
+        // test the getIngressLinks() method with a null connect point.
+        linkService.getIngressLinks(null);
+    }
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java
new file mode 100644
index 0000000..9156c38
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkManagerTest.java
@@ -0,0 +1,1003 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Lists;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestTools;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.DefaultVirtualNetwork;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+import org.onosproject.incubator.net.virtual.VirtualLink;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkEvent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkGroupStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntent;
+import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.impl.provider.DefaultVirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.impl.provider.DefaultVirtualGroupProvider;
+import org.onosproject.incubator.net.virtual.impl.provider.DefaultVirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.impl.provider.DefaultVirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.impl.provider.VirtualProviderManager;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowRuleStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualGroupStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualIntentStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualPacketStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MockIdGenerator;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+import static org.onosproject.net.NetTestTools.APP_ID;
+
+/**
+ * Junit tests for VirtualNetworkManager.
+ */
+public class VirtualNetworkManagerTest extends VirtualNetworkTestUtil {
+    private final String tenantIdValue1 = "TENANT_ID1";
+    private final String tenantIdValue2 = "TENANT_ID2";
+
+    private VirtualNetworkManager manager;
+    private DefaultVirtualNetworkProvider topologyProvider;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private CoreService coreService;
+    private TestListener listener = new TestListener();
+    private TopologyService topologyService;
+
+    private ConnectPoint cp6;
+    private ConnectPoint cp7;
+
+    private TestServiceDirectory testDirectory;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+        MockIdGenerator.cleanBind();
+
+        coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService",
+                           new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.addListener(listener);
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.removeListener(listener);
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+        MockIdGenerator.cleanBind();
+    }
+
+    /**
+     * Tests registering a null tenant id.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testRegisterNullTenantId() {
+        manager.registerTenantId(null);
+    }
+
+    /**
+     * Tests registering/unregistering a tenant id.
+     */
+    @Test
+    public void testRegisterUnregisterTenantId() {
+        manager.unregisterTenantId(TenantId.tenantId(tenantIdValue1));
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue2));
+        Collection<TenantId> tenantIdCollection = manager.getTenantIds();
+        assertEquals("The tenantId set size did not match.", 2, tenantIdCollection.size());
+
+        manager.unregisterTenantId(TenantId.tenantId(tenantIdValue1));
+        manager.unregisterTenantId(TenantId.tenantId(tenantIdValue2));
+        tenantIdCollection = manager.getTenantIds();
+        assertTrue("The tenantId set should be empty.", tenantIdCollection.isEmpty());
+
+        // Validate that the events were all received in the correct order.
+        validateEvents(VirtualNetworkEvent.Type.TENANT_REGISTERED,
+                       VirtualNetworkEvent.Type.TENANT_REGISTERED,
+                       VirtualNetworkEvent.Type.TENANT_UNREGISTERED,
+                       VirtualNetworkEvent.Type.TENANT_UNREGISTERED);
+    }
+
+    /**
+     * Test method {@code getTenantId()} for registered virtual network.
+     */
+    @Test
+    public void testGetTenantIdForRegisteredVirtualNetwork() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology(tenantIdValue1);
+        TenantId tenantId = manager.getTenantId(virtualNetwork.id());
+
+        assertThat(tenantId.toString(), is(tenantIdValue1));
+    }
+
+    /**
+     * Test method {@code getTenantId()} for null virtual network id.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetTenantIdForNullVirtualNetwork() {
+        manager.getTenantId(null);
+    }
+
+    /**
+     * Test method {@code getVirtualNetwork()} for registered virtual network.
+     */
+    @Test
+    public void testGetVirtualNetworkForRegisteredNetwork() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology(tenantIdValue1);
+
+        assertNotNull("Registered virtual network is null", manager.getVirtualNetwork(virtualNetwork.id()));
+    }
+
+    /**
+     * Test method {@code getVirtualNetwork()} for null virtual network id.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetVirtualForNullVirtualNetworkId() {
+        manager.getVirtualNetwork(null);
+    }
+
+    /**
+     * Tests adding a null virtual network.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullVirtualNetwork() {
+        manager.createVirtualNetwork(null);
+    }
+
+    /**
+     * Tests removal of a virtual network twice.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testRemoveVnetTwice() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        manager.removeVirtualNetwork(virtualNetwork.id());
+        manager.removeVirtualNetwork(virtualNetwork.id());
+    }
+
+    /**
+     * Tests add and remove of virtual networks.
+     */
+    @Test
+    public void testAddRemoveVirtualNetwork() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        Set<VirtualNetwork> virtualNetworks = manager.getVirtualNetworks(TenantId.tenantId(tenantIdValue1));
+        assertNotNull("The virtual network set should not be null", virtualNetworks);
+        assertEquals("The virtual network set size did not match.", 2, virtualNetworks.size());
+
+        int remaining = virtualNetworks.size();
+        for (VirtualNetwork virtualNetwork : virtualNetworks) {
+            manager.removeVirtualNetwork(virtualNetwork.id());
+            assertEquals("The expected virtual network size does not match",
+                         --remaining, manager.getVirtualNetworks(TenantId.tenantId(tenantIdValue1)).size());
+        }
+        virtualNetworks = manager.getVirtualNetworks(TenantId.tenantId(tenantIdValue1));
+        assertTrue("The virtual network set should be empty.", virtualNetworks.isEmpty());
+
+        // Create/remove a virtual network.
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        manager.removeVirtualNetwork(virtualNetwork.id());
+
+        virtualNetworks = manager.getVirtualNetworks(TenantId.tenantId(tenantIdValue1));
+        assertTrue("The virtual network set should be empty.", virtualNetworks.isEmpty());
+
+        // Validate that the events were all received in the correct order.
+        validateEvents(VirtualNetworkEvent.Type.TENANT_REGISTERED,
+                       VirtualNetworkEvent.Type.NETWORK_ADDED,
+                       VirtualNetworkEvent.Type.NETWORK_ADDED,
+                       VirtualNetworkEvent.Type.NETWORK_REMOVED,
+                       VirtualNetworkEvent.Type.NETWORK_REMOVED,
+                       VirtualNetworkEvent.Type.NETWORK_ADDED,
+                       VirtualNetworkEvent.Type.NETWORK_REMOVED);
+    }
+
+    /**
+     * Tests adding a null virtual device.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullVirtualDevice() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        manager.createVirtualDevice(virtualNetwork.id(), null);
+    }
+
+    /**
+     * Tests adding a virtual device where no virtual network exists.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testCreateVirtualDeviceWithNoNetwork() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork =
+                new DefaultVirtualNetwork(NetworkId.NONE,
+                                          TenantId.tenantId(tenantIdValue1));
+
+        manager.createVirtualDevice(virtualNetwork.id(), DID1);
+    }
+
+    /**
+     * Tests add and remove of virtual devices.
+     */
+    @Test
+    public void testAddRemoveVirtualDevice() {
+        List<VirtualNetworkEvent.Type> expectedEventTypes = new ArrayList<>();
+
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        expectedEventTypes.add(VirtualNetworkEvent.Type.TENANT_REGISTERED);
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        expectedEventTypes.add(VirtualNetworkEvent.Type.NETWORK_ADDED);
+        VirtualNetwork virtualNetwork2 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        expectedEventTypes.add(VirtualNetworkEvent.Type.NETWORK_ADDED);
+        manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_DEVICE_ADDED);
+        manager.createVirtualDevice(virtualNetwork2.id(), DID2);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_DEVICE_ADDED);
+
+        Set<VirtualDevice> virtualDevices1 = manager.getVirtualDevices(virtualNetwork1.id());
+        assertNotNull("The virtual device set should not be null", virtualDevices1);
+        assertEquals("The virtual device set size did not match.", 1, virtualDevices1.size());
+
+        Set<VirtualDevice> virtualDevices2 = manager.getVirtualDevices(virtualNetwork2.id());
+        assertNotNull("The virtual device set should not be null", virtualDevices2);
+        assertEquals("The virtual device set size did not match.", 1, virtualDevices2.size());
+
+        for (VirtualDevice virtualDevice : virtualDevices1) {
+            manager.removeVirtualDevice(virtualNetwork1.id(), virtualDevice.id());
+            expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_DEVICE_REMOVED);
+            // attempt to remove the same virtual device again - no event expected.
+            manager.removeVirtualDevice(virtualNetwork1.id(), virtualDevice.id());
+        }
+        virtualDevices1 = manager.getVirtualDevices(virtualNetwork1.id());
+        assertTrue("The virtual device set should be empty.", virtualDevices1.isEmpty());
+
+        // Add/remove the virtual device again.
+        VirtualDevice virtualDevice = manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_DEVICE_ADDED);
+        manager.removeVirtualDevice(virtualDevice.networkId(), virtualDevice.id());
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_DEVICE_REMOVED);
+        virtualDevices1 = manager.getVirtualDevices(virtualNetwork1.id());
+        assertTrue("The virtual device set should be empty.", virtualDevices1.isEmpty());
+
+        // Validate that the events were all received in the correct order.
+        validateEvents(expectedEventTypes.toArray(
+                new VirtualNetworkEvent.Type[expectedEventTypes.size()]));
+    }
+
+    /**
+     * Tests getting a collection of physical device identifier corresponding to
+     * the specified virtual device.
+     */
+    @Test
+    public void testGetPhysicalDevices() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue2));
+
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork2 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue2));
+
+        // two virtual device in first virtual network
+        VirtualDevice vDevice1InVnet1 =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        VirtualDevice vDevice2InVnet1 =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID2);
+        // Two virtual device in second virtual network
+        VirtualDevice vDevice1InVnet2 =
+                manager.createVirtualDevice(virtualNetwork2.id(), DID1);
+        VirtualDevice vDevice2InVnet2 =
+                manager.createVirtualDevice(virtualNetwork2.id(), DID2);
+
+        // Connection Point from each physical device
+        // Virtual network 1
+        ConnectPoint cp1InVnet1 =
+                new ConnectPoint(PHYDID1, PortNumber.portNumber(10));
+        ConnectPoint cp2InVnet1 =
+                new ConnectPoint(PHYDID2, PortNumber.portNumber(20));
+        ConnectPoint cp3InVnet1 =
+                new ConnectPoint(PHYDID3, PortNumber.portNumber(30));
+        ConnectPoint cp4InVnet1 =
+                new ConnectPoint(PHYDID4, PortNumber.portNumber(40));
+        // Virtual network 2
+        ConnectPoint cp1InVnet2 =
+                new ConnectPoint(PHYDID1, PortNumber.portNumber(10));
+        ConnectPoint cp2InVnet2 =
+                new ConnectPoint(PHYDID2, PortNumber.portNumber(20));
+        ConnectPoint cp3InVnet2 =
+                new ConnectPoint(PHYDID3, PortNumber.portNumber(30));
+        ConnectPoint cp4InVnet2 =
+                new ConnectPoint(PHYDID4, PortNumber.portNumber(40));
+
+        // Make simple BigSwitch by mapping two phyDevice to one vDevice
+        // First vDevice in first virtual network
+        manager.createVirtualPort(virtualNetwork1.id(),
+                vDevice1InVnet1.id(), PortNumber.portNumber(1), cp1InVnet1);
+        manager.createVirtualPort(virtualNetwork1.id(),
+                vDevice1InVnet1.id(), PortNumber.portNumber(2), cp2InVnet1);
+        // Second vDevice in first virtual network
+        manager.createVirtualPort(virtualNetwork1.id(),
+                vDevice2InVnet1.id(), PortNumber.portNumber(1), cp3InVnet1);
+        manager.createVirtualPort(virtualNetwork1.id(),
+                vDevice2InVnet1.id(), PortNumber.portNumber(2), cp4InVnet1);
+        // First vDevice in second virtual network
+        manager.createVirtualPort(virtualNetwork2.id(),
+                vDevice1InVnet2.id(), PortNumber.portNumber(1), cp1InVnet2);
+        manager.createVirtualPort(virtualNetwork2.id(),
+                vDevice1InVnet2.id(), PortNumber.portNumber(2), cp2InVnet2);
+        // Second vDevice in second virtual network
+        manager.createVirtualPort(virtualNetwork2.id(),
+                vDevice2InVnet2.id(), PortNumber.portNumber(1), cp3InVnet2);
+        manager.createVirtualPort(virtualNetwork2.id(),
+                vDevice2InVnet2.id(), PortNumber.portNumber(2), cp4InVnet2);
+
+
+        Set<DeviceId> physicalDeviceSet;
+        Set<DeviceId> testSet = new HashSet<>();
+        physicalDeviceSet = manager.getPhysicalDevices(virtualNetwork1.id(), vDevice1InVnet1.id());
+        testSet.add(PHYDID1);
+        testSet.add(PHYDID2);
+        assertEquals("The physical devices 1 did not match", testSet, physicalDeviceSet);
+        testSet.clear();
+
+        physicalDeviceSet = manager.getPhysicalDevices(virtualNetwork1.id(), vDevice2InVnet1.id());
+        testSet.add(PHYDID3);
+        testSet.add(PHYDID4);
+        assertEquals("The physical devices 2 did not match", testSet, physicalDeviceSet);
+        testSet.clear();
+
+        physicalDeviceSet = manager.getPhysicalDevices(virtualNetwork2.id(), vDevice1InVnet2.id());
+        testSet.add(PHYDID1);
+        testSet.add(PHYDID2);
+        assertEquals("The physical devices 1 did not match", testSet, physicalDeviceSet);
+        testSet.clear();
+
+        physicalDeviceSet = manager.getPhysicalDevices(virtualNetwork2.id(), vDevice2InVnet2.id());
+        testSet.add(PHYDID3);
+        testSet.add(PHYDID4);
+        assertEquals("The physical devices 2 did not match", testSet, physicalDeviceSet);
+        testSet.clear();
+    }
+
+    /**
+     * Tests adding a null virtual host.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testCreateNullVirtualHost() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        manager.createVirtualHost(virtualNetwork.id(), null, null, null, null, null);
+    }
+
+    /**
+     * Tests adding a virtual host where no virtual network exists.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testCreateVirtualHostWithNoNetwork() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork =
+                new DefaultVirtualNetwork(NetworkId.NONE, TenantId.tenantId(tenantIdValue1));
+
+        manager.createVirtualHost(virtualNetwork.id(), HID1, null, null, null, null);
+    }
+
+    /**
+     * Tests adding a virtual host where no virtual port exists.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testCreateVirtualHostWithNoVirtualPort() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        manager.createVirtualHost(virtualNetwork1.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
+    }
+
+    /**
+     * Tests add and remove of virtual hosts.
+     */
+    @Test
+    public void testAddRemoveVirtualHost() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork2 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork2.id(), DID2);
+
+        ConnectPoint hostCp1 = new ConnectPoint(DID1, P1);
+        ConnectPoint hostCp2 = new ConnectPoint(DID2, P2);
+        manager.createVirtualPort(virtualNetwork1.id(), hostCp1.deviceId(), hostCp1.port(),
+                new ConnectPoint(virtualDevice1.id(), hostCp1.port()));
+        manager.createVirtualPort(virtualNetwork2.id(), hostCp2.deviceId(), hostCp2.port(),
+                new ConnectPoint(virtualDevice2.id(), hostCp2.port()));
+
+        manager.createVirtualHost(virtualNetwork1.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
+        manager.createVirtualHost(virtualNetwork2.id(), HID2, MAC2, VLAN2, LOC2, IPSET2);
+
+        Set<VirtualHost> virtualHosts1 = manager.getVirtualHosts(virtualNetwork1.id());
+        assertNotNull("The virtual host set should not be null", virtualHosts1);
+        assertEquals("The virtual host set size did not match.", 1, virtualHosts1.size());
+
+        Set<VirtualHost> virtualHosts2 = manager.getVirtualHosts(virtualNetwork2.id());
+        assertNotNull("The virtual host set should not be null", virtualHosts2);
+        assertEquals("The virtual host set size did not match.", 1, virtualHosts2.size());
+
+        for (VirtualHost virtualHost : virtualHosts1) {
+            manager.removeVirtualHost(virtualNetwork1.id(), virtualHost.id());
+            // attempt to remove the same virtual host again.
+            manager.removeVirtualHost(virtualNetwork1.id(), virtualHost.id());
+        }
+        virtualHosts1 = manager.getVirtualHosts(virtualNetwork1.id());
+        assertTrue("The virtual host set should be empty.", virtualHosts1.isEmpty());
+
+        // Add/remove the virtual host again.
+        VirtualHost virtualHost =
+                manager.createVirtualHost(virtualNetwork1.id(),
+                                          HID1, MAC1, VLAN1, LOC1, IPSET1);
+        manager.removeVirtualHost(virtualHost.networkId(), virtualHost.id());
+        virtualHosts1 = manager.getVirtualHosts(virtualNetwork1.id());
+        assertTrue("The virtual host set should be empty.", virtualHosts1.isEmpty());
+    }
+
+    /**
+     * Tests add and remove of virtual links.
+     */
+    @Test
+    public void testAddRemoveVirtualLink() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID2);
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork1.id(), src.deviceId(), src.port(),
+                                  new ConnectPoint(srcVirtualDevice.id(), src.port()));
+
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork1.id(), dst.deviceId(), dst.port(),
+                                  new ConnectPoint(dstVirtualDevice.id(), dst.port()));
+
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork1.id(), dst, src);
+
+        Set<VirtualLink> virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
+        assertNotNull("The virtual link set should not be null", virtualLinks);
+        assertEquals("The virtual link set size did not match.", 2, virtualLinks.size());
+
+        for (VirtualLink virtualLink : virtualLinks) {
+            manager.removeVirtualLink(virtualLink.networkId(), virtualLink.src(), virtualLink.dst());
+            // attempt to remove the same virtual link again.
+            manager.removeVirtualLink(virtualLink.networkId(), virtualLink.src(), virtualLink.dst());
+        }
+        virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
+        assertTrue("The virtual link set should be empty.", virtualLinks.isEmpty());
+
+        // Add/remove the virtual link again.
+        VirtualLink virtualLink = manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.removeVirtualLink(virtualLink.networkId(), virtualLink.src(), virtualLink.dst());
+        virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
+        assertTrue("The virtual link set should be empty.", virtualLinks.isEmpty());
+    }
+
+    /**
+     * Tests adding the same virtual link twice.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testAddSameVirtualLink() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID2);
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork1.id(), src.deviceId(), src.port(),
+                                  new ConnectPoint(srcVirtualDevice.id(), src.port()));
+
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork1.id(), dst.deviceId(), dst.port(),
+                                  new ConnectPoint(dstVirtualDevice.id(), dst.port()));
+
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+    }
+
+    private VirtualPort getPort(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+        Set<VirtualPort> virtualPorts = manager.getVirtualPorts(networkId, deviceId);
+        return  virtualPorts.stream().filter(virtualPort -> virtualPort.number().equals(portNumber))
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Tests add, bind and remove of virtual ports.
+     */
+    @Test
+    public void testAddRemoveVirtualPort() {
+        List<VirtualNetworkEvent.Type> expectedEventTypes = new ArrayList<>();
+
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        expectedEventTypes.add(VirtualNetworkEvent.Type.TENANT_REGISTERED);
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        expectedEventTypes.add(VirtualNetworkEvent.Type.NETWORK_ADDED);
+        VirtualDevice virtualDevice =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_DEVICE_ADDED);
+        ConnectPoint cp = new ConnectPoint(virtualDevice.id(), PortNumber.portNumber(1));
+
+        manager.createVirtualPort(virtualNetwork1.id(),
+                                  virtualDevice.id(), PortNumber.portNumber(1), cp);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_ADDED);
+        manager.createVirtualPort(virtualNetwork1.id(),
+                                  virtualDevice.id(), PortNumber.portNumber(2), cp);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_ADDED);
+
+        Set<VirtualPort> virtualPorts = manager.getVirtualPorts(virtualNetwork1.id(), virtualDevice.id());
+        assertNotNull("The virtual port set should not be null", virtualPorts);
+        assertEquals("The virtual port set size did not match.", 2, virtualPorts.size());
+        virtualPorts.forEach(vp -> assertFalse("Initial virtual port state should be disabled", vp.isEnabled()));
+
+        // verify change state of virtual port (disabled -> enabled)
+        manager.updatePortState(virtualNetwork1.id(), virtualDevice.id(), PortNumber.portNumber(1), true);
+        VirtualPort changedPort = getPort(virtualNetwork1.id(), virtualDevice.id(), PortNumber.portNumber(1));
+        assertNotNull("The changed virtual port should not be null", changedPort);
+        assertEquals("Virtual port state should be enabled", true, changedPort.isEnabled());
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_UPDATED);
+
+        // verify change state of virtual port (disabled -> disabled)
+        manager.updatePortState(virtualNetwork1.id(), virtualDevice.id(), PortNumber.portNumber(2), false);
+        changedPort = getPort(virtualNetwork1.id(), virtualDevice.id(), PortNumber.portNumber(2));
+        assertNotNull("The changed virtual port should not be null", changedPort);
+        assertEquals("Virtual port state should be disabled", false, changedPort.isEnabled());
+        // no VIRTUAL_PORT_UPDATED event is expected - the requested state (disabled) is same as previous state.
+
+        for (VirtualPort virtualPort : virtualPorts) {
+            manager.removeVirtualPort(virtualNetwork1.id(),
+                                      (DeviceId) virtualPort.element().id(), virtualPort.number());
+            expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_REMOVED);
+            // attempt to remove the same virtual port again.
+            manager.removeVirtualPort(virtualNetwork1.id(),
+                                      (DeviceId) virtualPort.element().id(), virtualPort.number());
+        }
+        virtualPorts = manager.getVirtualPorts(virtualNetwork1.id(), virtualDevice.id());
+        assertTrue("The virtual port set should be empty.", virtualPorts.isEmpty());
+
+        // Add/remove the virtual port again.
+        VirtualPort virtualPort =
+                manager.createVirtualPort(virtualNetwork1.id(), virtualDevice.id(),
+                                                            PortNumber.portNumber(1), cp);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_ADDED);
+
+        ConnectPoint newCp = new ConnectPoint(DID2, PortNumber.portNumber(2));
+        manager.bindVirtualPort(virtualNetwork1.id(), virtualDevice.id(),
+                                PortNumber.portNumber(1), newCp);
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_UPDATED);
+
+        manager.removeVirtualPort(virtualNetwork1.id(),
+                                  (DeviceId) virtualPort.element().id(), virtualPort.number());
+        expectedEventTypes.add(VirtualNetworkEvent.Type.VIRTUAL_PORT_REMOVED);
+        virtualPorts = manager.getVirtualPorts(virtualNetwork1.id(), virtualDevice.id());
+        assertTrue("The virtual port set should be empty.", virtualPorts.isEmpty());
+
+        // Validate that the events were all received in the correct order.
+        validateEvents(expectedEventTypes.toArray(
+                new VirtualNetworkEvent.Type[expectedEventTypes.size()]));
+    }
+
+    /**
+     * Tests when a virtual element is removed, all the other elements depending on it are also removed.
+     */
+    @Test
+    public void testRemoveAllElements() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork1 =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork1.id(), DID2);
+        ConnectPoint src = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork1.id(), src.deviceId(), src.port(),
+                new ConnectPoint(PHYDID1, PortNumber.portNumber(1)));
+
+        ConnectPoint dst = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork1.id(), dst.deviceId(), dst.port(),
+                new ConnectPoint(PHYDID2, PortNumber.portNumber(2)));
+
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork1.id(), dst, src);
+
+        ConnectPoint hostCp = new ConnectPoint(DID1, P1);
+        manager.createVirtualPort(virtualNetwork1.id(), hostCp.deviceId(), hostCp.port(),
+                new ConnectPoint(PHYDID1, P1));
+        manager.createVirtualHost(virtualNetwork1.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
+
+        //When a virtual port is removed, all virtual links connected to it should also be removed.
+        manager.removeVirtualPort(virtualNetwork1.id(), DID1, PortNumber.portNumber(1));
+        Set<VirtualLink> virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
+        assertTrue("The virtual link set should be empty.", virtualLinks.isEmpty());
+
+        //When a virtual port is removed, all virtual hosts located to it should also be removed.
+        manager.removeVirtualPort(virtualNetwork1.id(), DID1, P1);
+        Set<VirtualHost> virtualHosts = manager.getVirtualHosts(virtualNetwork1.id());
+        assertTrue("The virtual host set should be empty.", virtualHosts.isEmpty());
+
+        manager.createVirtualPort(virtualNetwork1.id(), src.deviceId(), src.port(),
+                new ConnectPoint(PHYDID1, PortNumber.portNumber(1)));
+        manager.createVirtualLink(virtualNetwork1.id(), src, dst);
+        manager.createVirtualLink(virtualNetwork1.id(), dst, src);
+        manager.createVirtualPort(virtualNetwork1.id(), hostCp.deviceId(), hostCp.port(),
+                new ConnectPoint(PHYDID1, P1));
+        manager.createVirtualHost(virtualNetwork1.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
+
+        //When a virtual device is removed, all virtual ports, hosts and links depended on it should also be removed.
+        manager.removeVirtualDevice(virtualNetwork1.id(), DID1);
+        Set<VirtualPort> virtualPorts = manager.getVirtualPorts(virtualNetwork1.id(), DID1);
+        assertTrue("The virtual port set of DID1 should be empty", virtualPorts.isEmpty());
+        virtualLinks = manager.getVirtualLinks(virtualNetwork1.id());
+        assertTrue("The virtual link set should be empty.", virtualLinks.isEmpty());
+        virtualHosts = manager.getVirtualHosts(virtualNetwork1.id());
+        assertTrue("The virtual host set should be empty.", virtualHosts.isEmpty());
+
+        //When a tenantId is removed, all the virtual networks belonging to it should also be removed.
+        manager.unregisterTenantId(TenantId.tenantId(tenantIdValue1));
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        Set<VirtualNetwork> virtualNetworks = manager.getVirtualNetworks(TenantId.tenantId(tenantIdValue1));
+        assertNotNull("The virtual network set should not be null", virtualNetworks);
+        assertTrue("The virtual network set should be empty.", virtualNetworks.isEmpty());
+    }
+
+    /**
+     * Tests the addTunnelId() method in the store with a null intent.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testAddTunnelIdNullIntent() {
+        manager.store.addTunnelId(null, null);
+    }
+
+    /**
+     * Tests the removeTunnelId() method in the store with a null intent.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testRemoveTunnelIdNullIntent() {
+        manager.store.removeTunnelId(null, null);
+    }
+
+    /**
+     * Tests the addTunnelId, getTunnelIds(), removeTunnelId() methods with the store.
+     */
+    @Test
+    public void testAddTunnelId() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        ConnectPoint cp1 = new ConnectPoint(DID1, P1);
+        ConnectPoint cp2 = new ConnectPoint(DID2, P1);
+
+        VirtualNetworkIntent virtualIntent = VirtualNetworkIntent.builder()
+                .networkId(virtualNetwork.id())
+                .key(Key.of("Test", APP_ID))
+                .appId(APP_ID)
+                .ingressPoint(cp1)
+                .egressPoint(cp2)
+                .build();
+
+        TunnelId tunnelId = TunnelId.valueOf("virtual tunnel");
+        // Add the intent to tunnelID mapping to the store.
+        manager.store.addTunnelId(virtualIntent, tunnelId);
+        assertEquals("The tunnels size should match.", 1,
+                     manager.store.getTunnelIds(virtualIntent).size());
+
+        // Remove the intent to tunnelID mapping from the store.
+        manager.store.removeTunnelId(virtualIntent, tunnelId);
+        assertTrue("The tunnels should be empty.",
+                   manager.store.getTunnelIds(virtualIntent).isEmpty());
+    }
+
+
+    /**
+     * Method to create the virtual network for {@code tenantIdValue} for further testing.
+     **/
+    private VirtualNetwork setupVirtualNetworkTopology(String tenantIdValue) {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue));
+        VirtualNetwork virtualNetwork =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue));
+
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        VirtualDevice virtualDevice3 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID3);
+        VirtualDevice virtualDevice4 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID4);
+        VirtualDevice virtualDevice5 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID5);
+
+        ConnectPoint cp1 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice1.id(),
+                                  PortNumber.portNumber(1), cp1);
+
+        ConnectPoint cp2 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice1.id(),
+                                  PortNumber.portNumber(2), cp2);
+
+        ConnectPoint cp3 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(3));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice2.id(),
+                                  PortNumber.portNumber(3), cp3);
+
+        ConnectPoint cp4 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(4));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice2.id(),
+                                  PortNumber.portNumber(4), cp4);
+
+        ConnectPoint cp5 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(5));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice3.id(),
+                                  PortNumber.portNumber(5), cp5);
+
+        cp6 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(6));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice3.id(),
+                                  PortNumber.portNumber(6), cp6);
+
+        cp7 = new ConnectPoint(virtualDevice4.id(), PortNumber.portNumber(7));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice4.id(),
+                                  PortNumber.portNumber(7), cp7);
+
+        ConnectPoint cp8 = new ConnectPoint(virtualDevice4.id(), PortNumber.portNumber(8));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice4.id(),
+                                  PortNumber.portNumber(8), cp8);
+
+        ConnectPoint cp9 = new ConnectPoint(virtualDevice5.id(), PortNumber.portNumber(9));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice5.id(),
+                                  PortNumber.portNumber(9), cp9);
+
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), cp1, cp3);
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), cp3, cp1);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link3 = manager.createVirtualLink(virtualNetwork.id(), cp4, cp5);
+        virtualNetworkManagerStore.updateLink(link3, link3.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link4 = manager.createVirtualLink(virtualNetwork.id(), cp5, cp4);
+        virtualNetworkManagerStore.updateLink(link4, link4.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link5 = manager.createVirtualLink(virtualNetwork.id(), cp8, cp9);
+        virtualNetworkManagerStore.updateLink(link5, link5.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link6 = manager.createVirtualLink(virtualNetwork.id(), cp9, cp8);
+        virtualNetworkManagerStore.updateLink(link6, link6.tunnelId(), Link.State.ACTIVE);
+
+        topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        topologyProvider = new DefaultVirtualNetworkProvider();
+        try {
+            TestUtils.setField(topologyProvider, "topologyService", topologyService);
+        } catch (TestUtils.TestUtilsException e) {
+            e.printStackTrace();
+        }
+//        topologyProvider.topologyService = topologyService;
+
+        return virtualNetwork;
+    }
+
+    /**
+     * Test the topologyChanged() method.
+     */
+    @Test
+    public void testTopologyChanged() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology(tenantIdValue1);
+        VirtualNetworkProviderService providerService =
+                manager.createProviderService(topologyProvider);
+
+        // Initial setup is two clusters of devices/links.
+        assertEquals("The cluster count did not match.", 2,
+                     topologyService.currentTopology().clusterCount());
+
+        // Adding this link will join the two clusters together.
+        List<Event> reasons = new ArrayList<>();
+        VirtualLink link = manager.createVirtualLink(virtualNetwork.id(), cp6, cp7);
+        virtualNetworkManagerStore.updateLink(link, link.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), cp7, cp6);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+
+        Topology topology = topologyService.currentTopology();
+        providerService.topologyChanged(topologyProvider.getConnectPoints(topology));
+
+        // Validate that all links are still active.
+        manager.getVirtualLinks(virtualNetwork.id()).forEach(virtualLink -> {
+            assertTrue("The virtual link should be active.",
+                       virtualLink.state().equals(Link.State.ACTIVE));
+        });
+
+        virtualNetworkManagerStore.updateLink(link, link.tunnelId(), Link.State.INACTIVE);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.INACTIVE);
+        providerService.topologyChanged(topologyProvider.getConnectPoints(topology));
+
+        // Validate that all links are active again.
+        manager.getVirtualLinks(virtualNetwork.id()).forEach(virtualLink -> {
+            assertTrue("The virtual link should be active.",
+                       virtualLink.state().equals(Link.State.ACTIVE));
+        });
+    }
+
+    /**
+     * Tests that the get() method returns saved service instances.
+     */
+    @Test
+    public void testServiceGetReturnsSavedInstance() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork =
+                manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), DeviceService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), LinkService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), TopologyService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), HostService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), PathService.class);
+
+        // extra setup needed for FlowRuleService, PacketService, GroupService, and IntentService
+        VirtualProviderManager virtualProviderManager = new VirtualProviderManager();
+        virtualProviderManager.registerProvider(new DefaultVirtualFlowRuleProvider());
+        virtualProviderManager.registerProvider(new DefaultVirtualPacketProvider());
+        virtualProviderManager.registerProvider(new DefaultVirtualGroupProvider());
+        testDirectory.add(CoreService.class, coreService)
+                .add(VirtualProviderRegistryService.class, virtualProviderManager)
+                .add(EventDeliveryService.class, new TestEventDispatcher())
+                .add(ClusterService.class, new ClusterServiceAdapter())
+                .add(VirtualNetworkFlowRuleStore.class, new SimpleVirtualFlowRuleStore())
+                .add(VirtualNetworkPacketStore.class, new SimpleVirtualPacketStore())
+                .add(VirtualNetworkGroupStore.class, new SimpleVirtualGroupStore())
+                .add(VirtualNetworkIntentStore.class, new SimpleVirtualIntentStore())
+                .add(VirtualNetworkFlowObjectiveStore.class, new SimpleVirtualFlowObjectiveStore());
+
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), FlowRuleService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), FlowObjectiveService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), PacketService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), GroupService.class);
+        validateServiceGetReturnsSavedInstance(virtualNetwork.id(), IntentService.class);
+    }
+
+    /**
+     * Validates that the get() method returns saved service instances.
+     */
+    private <T> void validateServiceGetReturnsSavedInstance(NetworkId networkId,
+                                                            Class<T> serviceClass) {
+        T serviceInstanceFirst = manager.get(networkId, serviceClass);
+        T serviceInstanceSubsequent = manager.get(networkId, serviceClass);
+        assertSame(serviceClass.getSimpleName() +
+                     ": Subsequent get should be same as the first one",
+                     serviceInstanceFirst, serviceInstanceSubsequent);
+    }
+
+    /**
+     * Method to validate that the actual versus expected virtual network events were
+     * received correctly.
+     *
+     * @param types expected virtual network events.
+     */
+    private void validateEvents(Enum... types) {
+        TestTools.assertAfter(100, () -> {
+            int i = 0;
+            assertEquals("wrong events received", types.length, listener.events.size());
+            for (Event event : listener.events) {
+                assertEquals("incorrect event type", types[i], event.type());
+                i++;
+            }
+            listener.events.clear();
+        });
+    }
+
+    /**
+     * Test listener class to receive virtual network events.
+     */
+    private static class TestListener implements VirtualNetworkListener {
+
+        private List<VirtualNetworkEvent> events = Lists.newArrayList();
+
+        @Override
+        public void event(VirtualNetworkEvent event) {
+            events.add(event);
+        }
+
+    }
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManagerTest.java
new file mode 100644
index 0000000..a08ab5c
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMastershipManagerTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualMastershipStore;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipTermService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.onosproject.net.MastershipRole.MASTER;
+import static org.onosproject.net.MastershipRole.STANDBY;
+import static org.onosproject.net.MastershipRole.NONE;
+
+public class VirtualNetworkMastershipManagerTest {
+
+    private static final NodeId NID_LOCAL = new NodeId("local");
+    private static final NodeId NID_OTHER = new NodeId("foo");
+    private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
+
+    private static final TenantId TID = TenantId.tenantId("1");
+
+    private static final DeviceId VDID1 = DeviceId.deviceId("foo:vd1");
+    private static final DeviceId VDID2 = DeviceId.deviceId("foo:vd2");
+    private static final DeviceId VDID3 = DeviceId.deviceId("foo:vd3");
+    private static final DeviceId VDID4 = DeviceId.deviceId("foo:vd4");
+
+    private static final NodeId NID1 = NodeId.nodeId("n1");
+    private static final NodeId NID2 = NodeId.nodeId("n2");
+    private static final NodeId NID3 = NodeId.nodeId("n3");
+    private static final NodeId NID4 = NodeId.nodeId("n4");
+
+    private static final ControllerNode CNODE1 =
+            new DefaultControllerNode(NID1, IpAddress.valueOf("127.0.1.1"));
+    private static final ControllerNode CNODE2 =
+            new DefaultControllerNode(NID2, IpAddress.valueOf("127.0.1.2"));
+    private static final ControllerNode CNODE3 =
+            new DefaultControllerNode(NID3, IpAddress.valueOf("127.0.1.3"));
+    private static final ControllerNode CNODE4 =
+            new DefaultControllerNode(NID4, IpAddress.valueOf("127.0.1.4"));
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+
+    private VirtualNetworkMastershipManager mastershipMgr1;
+    private VirtualNetworkMastershipManager mastershipMgr2;
+    protected MastershipService service;
+    private TestClusterService testClusterService;
+    private EventDeliveryService eventDeliveryService;
+
+    private VirtualNetwork vnet1;
+    private VirtualNetwork vnet2;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        CoreService coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        TestUtils.setField(manager, "coreService", coreService);
+
+        eventDeliveryService = new TestEventDispatcher();
+        NetTestTools.injectEventDispatcher(manager, eventDeliveryService);
+
+        SimpleVirtualMastershipStore store = new SimpleVirtualMastershipStore();
+        TestUtils.setField(store, "coreService", coreService);
+        store.activate();
+
+        testClusterService = new TestClusterService();
+
+        ServiceDirectory testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(CoreService.class, coreService)
+                .add(EventDeliveryService.class, eventDeliveryService)
+                .add(ClusterService.class, testClusterService)
+                .add(VirtualNetworkMastershipStore.class, store);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+
+        createVnets();
+
+        mastershipMgr1 = new VirtualNetworkMastershipManager(manager, vnet1.id());
+        mastershipMgr2 = new VirtualNetworkMastershipManager(manager, vnet2.id());
+        service = mastershipMgr1;
+    }
+
+    private void createVnets() {
+        manager.registerTenantId(TID);
+
+        vnet1 = manager.createVirtualNetwork(TID);
+        manager.createVirtualDevice(vnet1.id(), VDID1);
+        manager.createVirtualDevice(vnet1.id(), VDID2);
+
+        vnet2 = manager.createVirtualNetwork(TID);
+        manager.createVirtualDevice(vnet2.id(), VDID3);
+        manager.createVirtualDevice(vnet2.id(), VDID4);
+    }
+
+    @After
+    public void tearDown() {
+        manager.deactivate();
+        virtualNetworkManagerStore.deactivate();
+    }
+
+    @Test
+    public void setRole() {
+        mastershipMgr1.setRole(NID_OTHER, VDID1, MASTER);
+        assertEquals("wrong local role:", NONE, mastershipMgr1.getLocalRole(VDID1));
+        assertEquals("wrong obtained role:", STANDBY, Futures.getUnchecked(mastershipMgr1.requestRoleFor(VDID1)));
+
+        //set to master
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        assertEquals("wrong local role:", MASTER, mastershipMgr1.getLocalRole(VDID1));
+    }
+
+    @Test
+    public void relinquishMastership() {
+        //no backups - should just turn to NONE for device.
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        assertEquals("wrong role:", MASTER, mastershipMgr1.getLocalRole(VDID1));
+        mastershipMgr1.relinquishMastership(VDID1);
+        assertNull("wrong master:", mastershipMgr1.getMasterFor(VDID2));
+        assertEquals("wrong role:", NONE, mastershipMgr1.getLocalRole(VDID1));
+
+        //not master, nothing should happen
+        mastershipMgr1.setRole(NID_LOCAL, VDID2, NONE);
+        mastershipMgr1.relinquishMastership(VDID2);
+        assertNull("wrong role:", mastershipMgr1.getMasterFor(VDID2));
+
+        //provide NID_OTHER as backup and relinquish
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr1.getMasterFor(VDID1));
+        mastershipMgr1.setRole(NID_OTHER, VDID1, STANDBY);
+        mastershipMgr1.relinquishMastership(VDID1);
+        assertEquals("wrong master:", NID_OTHER, mastershipMgr1.getMasterFor(VDID1));
+    }
+
+    @Test
+    public void requestRoleFor() {
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        mastershipMgr1.setRole(NID_OTHER, VDID2, MASTER);
+
+        //local should be master for one but standby for other
+        assertEquals("wrong role:", MASTER, Futures.getUnchecked(mastershipMgr1.requestRoleFor(VDID1)));
+        assertEquals("wrong role:", STANDBY, Futures.getUnchecked(mastershipMgr1.requestRoleFor(VDID2)));
+    }
+
+    @Test
+    public void getMasterFor() {
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        mastershipMgr1.setRole(NID_OTHER, VDID2, MASTER);
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr1.getMasterFor(VDID1));
+        assertEquals("wrong master:", NID_OTHER, mastershipMgr1.getMasterFor(VDID2));
+
+        //have NID_OTHER hand over VDID2 to NID_LOCAL
+        mastershipMgr1.setRole(NID_LOCAL, VDID2, MASTER);
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr1.getMasterFor(VDID2));
+    }
+
+    @Test
+    public void getDevicesOf() {
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        mastershipMgr1.setRole(NID_LOCAL, VDID2, STANDBY);
+        assertEquals("should be one device:", 1, mastershipMgr1.getDevicesOf(NID_LOCAL).size());
+        //hand both devices to NID_LOCAL
+        mastershipMgr1.setRole(NID_LOCAL, VDID2, MASTER);
+        assertEquals("should be two devices:", 2, mastershipMgr1.getDevicesOf(NID_LOCAL).size());
+    }
+
+    @Test
+    public void termService() {
+        MastershipTermService ts = mastershipMgr1;
+
+        //term = 1 for both
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        assertEquals("inconsistent term: ", 1,
+                     ts.getMastershipTerm(VDID1).termNumber());
+
+        //hand devices to NID_LOCAL and back: term = 1 + 2
+        mastershipMgr1.setRole(NID_OTHER, VDID1, MASTER);
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        assertEquals("inconsistent terms: ",
+                     3, ts.getMastershipTerm(VDID1).termNumber());
+    }
+
+    @Test
+    public void balanceWithVnets() {
+
+        testClusterService.put(CNODE1, ControllerNode.State.ACTIVE);
+        testClusterService.put(CNODE2, ControllerNode.State.ACTIVE);
+
+        mastershipMgr1.setRole(NID_LOCAL, VDID1, MASTER);
+        mastershipMgr1.setRole(NID_LOCAL, VDID2, MASTER);
+        assertEquals("wrong local role:", MASTER, mastershipMgr1.getLocalRole(VDID1));
+        assertEquals("wrong local role:", MASTER, mastershipMgr1.getLocalRole(VDID2));
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr1.getMasterFor(VDID1));
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr1.getMasterFor(VDID2));
+
+        //do balancing according to vnet Id.
+        mastershipMgr1.balanceRoles();
+        assertEquals("wrong master:", NID1, mastershipMgr1.getMasterFor(VDID1));
+        assertEquals("wrong master:", NID1, mastershipMgr1.getMasterFor(VDID2));
+
+        mastershipMgr2.setRole(NID_LOCAL, VDID3, MASTER);
+        mastershipMgr2.setRole(NID_LOCAL, VDID4, MASTER);
+        assertEquals("wrong local role:", MASTER, mastershipMgr2.getLocalRole(VDID3));
+        assertEquals("wrong local role:", MASTER, mastershipMgr2.getLocalRole(VDID4));
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr2.getMasterFor(VDID3));
+        assertEquals("wrong master:", NID_LOCAL, mastershipMgr2.getMasterFor(VDID4));
+
+        //do balancing according to vnet Id.
+        mastershipMgr2.balanceRoles();
+        assertEquals("wrong master:", NID2, mastershipMgr2.getMasterFor(VDID3));
+        assertEquals("wrong master:", NID2, mastershipMgr2.getMasterFor(VDID4));
+
+        // make N1 inactive
+        testClusterService.put(CNODE1, ControllerNode.State.INACTIVE);
+        mastershipMgr1.balanceRoles();
+        assertEquals("wrong master:", NID2, mastershipMgr1.getMasterFor(VDID1));
+        assertEquals("wrong master:", NID2, mastershipMgr1.getMasterFor(VDID2));
+    }
+
+    private final class TestClusterService extends ClusterServiceAdapter {
+
+        final Map<NodeId, ControllerNode> nodes = new HashMap<>();
+        final Map<NodeId, ControllerNode.State> nodeStates = new HashMap<>();
+
+        ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST);
+
+        @Override
+        public ControllerNode getLocalNode() {
+            return local;
+        }
+
+        @Override
+        public Set<ControllerNode> getNodes() {
+            return Sets.newHashSet(nodes.values());
+        }
+
+        @Override
+        public ControllerNode getNode(NodeId nodeId) {
+            return nodes.get(nodeId);
+        }
+
+        @Override
+        public ControllerNode.State getState(NodeId nodeId) {
+            return nodeStates.get(nodeId);
+        }
+
+        public void put(ControllerNode cn, ControllerNode.State state) {
+            nodes.put(cn.id(), cn);
+            nodeStates.put(cn.id(), state);
+        }
+    }
+
+    private final class TestSimpleMastershipStore extends SimpleVirtualMastershipStore
+            implements VirtualNetworkMastershipStore {
+
+        public TestSimpleMastershipStore(ClusterService clusterService) {
+            super.clusterService = clusterService;
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManagerTest.java
new file mode 100644
index 0000000..3eb6e88
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkMeterManagerTest.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Maps;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onosproject.TestApplicationId;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkMeterStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.event.VirtualListenerRegistryManager;
+import org.onosproject.incubator.net.virtual.impl.provider.VirtualProviderManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualMeterProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualMeterStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.intent.FakeIntentManager;
+import org.onosproject.net.intent.TestableIntentService;
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.DefaultBand;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.DefaultMeterFeatures;
+import org.onosproject.net.meter.DefaultMeterRequest;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterFeaturesKey;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterOperation;
+import org.onosproject.net.meter.MeterOperations;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterState;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * Virtual Network meter manager tests.
+ */
+public class VirtualNetworkMeterManagerTest extends VirtualNetworkTestUtil {
+    private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final NodeId NID_LOCAL = new NodeId("local");
+    private static final IpAddress LOCALHOST = IpAddress.valueOf("127.0.0.1");
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private TestableIntentService intentService = new FakeIntentManager();
+    private ServiceDirectory testDirectory;
+    private VirtualProviderManager providerRegistryService;
+
+    private EventDeliveryService eventDeliveryService;
+    VirtualListenerRegistryManager listenerRegistryManager =
+            VirtualListenerRegistryManager.getInstance();
+
+    private VirtualNetwork vnet1;
+    private VirtualNetwork vnet2;
+
+    private SimpleVirtualMeterStore meterStore;
+
+    private VirtualNetworkMeterManager meterManager1;
+    private VirtualNetworkMeterManager meterManager2;
+
+    private TestProvider provider = new TestProvider();
+    private VirtualMeterProviderService providerService1;
+    private VirtualMeterProviderService providerService2;
+
+    private ApplicationId appId;
+
+    private Meter m1;
+    private Meter m2;
+    private MeterRequest.Builder m1Request;
+    private MeterRequest.Builder m2Request;
+
+    private Map<MeterId, Meter> meters = Maps.newHashMap();
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+        CoreService coreService = new TestCoreService();
+        TestStorageService storageService = new TestStorageService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", storageService);
+        virtualNetworkManagerStore.activate();
+
+        meterStore = new SimpleVirtualMeterStore();
+
+        providerRegistryService = new VirtualProviderManager();
+        providerRegistryService.registerProvider(provider);
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        TestUtils.setField(manager, "coreService", coreService);
+
+        eventDeliveryService = new TestEventDispatcher();
+        NetTestTools.injectEventDispatcher(manager, eventDeliveryService);
+//        eventDeliveryService.addSink(VirtualEvent.class, listenerRegistryManager);
+
+        appId = new TestApplicationId("MeterManagerTest");
+
+        testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(CoreService.class, coreService)
+                .add(VirtualProviderRegistryService.class, providerRegistryService)
+                .add(EventDeliveryService.class, eventDeliveryService)
+                .add(StorageService.class, storageService)
+                .add(VirtualNetworkMeterStore.class, meterStore);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+
+        vnet1 = setupVirtualNetworkTopology(manager, TID1);
+        vnet2 = setupVirtualNetworkTopology(manager, TID2);
+
+        meterManager1 = new VirtualNetworkMeterManager(manager, vnet1.id());
+        meterManager2 = new VirtualNetworkMeterManager(manager, vnet2.id());
+
+        providerService1 = (VirtualMeterProviderService)
+                providerRegistryService.getProviderService(vnet1.id(), VirtualMeterProvider.class);
+        providerService2 = (VirtualMeterProviderService)
+                providerRegistryService.getProviderService(vnet2.id(), VirtualMeterProvider.class);
+
+        assertTrue("provider should be registered",
+                   providerRegistryService.getProviders().contains(provider.id()));
+
+        setupMeterTestVariables();
+    }
+
+    @After
+    public void tearDown() {
+        providerRegistryService.unregisterProvider(provider);
+        assertFalse("provider should not be registered",
+                    providerRegistryService.getProviders().contains(provider.id()));
+
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+
+        virtualNetworkManagerStore.deactivate();
+    }
+
+    /** Test for meter submit(). */
+    @Test
+    public void testAddition() {
+        meterManager1.submit(m1Request.add());
+
+        assertTrue("The meter was not added",
+                   meterManager1.getAllMeters().size() == 1);
+        assertThat(meterManager1.getMeter(VDID1, MeterId.meterId(1)), is(m1));
+
+        assertTrue("The meter shouldn't be added for vnet2",
+                   meterManager2.getAllMeters().size() == 0);
+    }
+
+    /** Test for meter remove(). */
+    @Test
+    public void testRemove() {
+        meterManager1.submit(m1Request.add());
+        meterManager1.withdraw(m1Request.remove(), m1.id());
+
+        assertThat(meterManager1.getMeter(VDID1, MeterId.meterId(1)).state(),
+                   is(MeterState.PENDING_REMOVE));
+
+        providerService1.pushMeterMetrics(m1.deviceId(), Collections.emptyList());
+
+        assertTrue("The meter was not removed", meterManager1.getAllMeters().size() == 0);
+        assertTrue("The meter shouldn't be added for vnet2",
+                   meterManager2.getAllMeters().size() == 0);
+    }
+
+    /** Test for meter submit with multiple devices. */
+    @Test
+    public void testMultipleDevice() {
+        meterManager1.submit(m1Request.add());
+        meterManager1.submit(m2Request.add());
+
+        assertTrue("The meters were not added",
+                   meterManager1.getAllMeters().size() == 2);
+        assertTrue("The meter shouldn't be added for vnet2",
+                   meterManager2.getAllMeters().size() == 0);
+
+        assertThat(meterManager1.getMeter(VDID1, MeterId.meterId(1)), is(m1));
+        assertThat(meterManager1.getMeter(VDID2, MeterId.meterId(1)), is(m2));
+    }
+
+    /** Test for meter features inside store. */
+    @Test
+    public void testMeterFeatures() {
+        //Test for virtual network 1
+        assertEquals(meterStore.getMaxMeters(vnet1.id(),
+                                             MeterFeaturesKey.key(VDID1)), 255L);
+        assertEquals(meterStore.getMaxMeters(vnet1.id(),
+                                             MeterFeaturesKey.key(VDID2)), 2);
+        //Test for virtual network 2
+        assertEquals(meterStore.getMaxMeters(vnet2.id(),
+                                             MeterFeaturesKey.key(VDID1)), 100);
+        assertEquals(meterStore.getMaxMeters(vnet2.id(),
+                                             MeterFeaturesKey.key(VDID2)), 10);
+    }
+
+    /** Set variables such as meters and request required for testing. */
+    private void setupMeterTestVariables() {
+        Band band = DefaultBand.builder()
+                .ofType(Band.Type.DROP)
+                .withRate(500)
+                .build();
+
+        m1 = DefaultMeter.builder()
+                .forDevice(VDID1)
+                .fromApp(appId)
+                .withId(MeterId.meterId(1))
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(band))
+                .build();
+
+        m2 = DefaultMeter.builder()
+                .forDevice(VDID2)
+                .fromApp(appId)
+                .withId(MeterId.meterId(1))
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(band))
+                .build();
+
+        m1Request = DefaultMeterRequest.builder()
+                .forDevice(VDID1)
+                .fromApp(appId)
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(band));
+
+        m2Request = DefaultMeterRequest.builder()
+                .forDevice(VDID2)
+                .fromApp(appId)
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withBands(Collections.singletonList(band));
+
+        meterStore.storeMeterFeatures(vnet1.id(),
+                                      DefaultMeterFeatures.builder().forDevice(VDID1)
+                                              .withMaxMeters(255L)
+                                              .withBandTypes(new HashSet<>())
+                                              .withUnits(new HashSet<>())
+                                              .hasStats(false)
+                                              .hasBurst(false)
+                                              .withMaxBands((byte) 0)
+                                              .withMaxColors((byte) 0)
+                                              .build());
+        meterStore.storeMeterFeatures(vnet1.id(),
+                                      DefaultMeterFeatures.builder().forDevice(VDID2)
+                                              .withMaxMeters(2)
+                                              .withBandTypes(new HashSet<>())
+                                              .withUnits(new HashSet<>())
+                                              .hasBurst(false)
+                                              .hasStats(false)
+                                              .withMaxBands((byte) 0)
+                                              .withMaxColors((byte) 0)
+                                              .build());
+
+        meterStore.storeMeterFeatures(vnet2.id(),
+                                      DefaultMeterFeatures.builder().forDevice(VDID1)
+                                              .withMaxMeters(100L)
+                                              .withBandTypes(new HashSet<>())
+                                              .withUnits(new HashSet<>())
+                                              .hasStats(false)
+                                              .hasBurst(false)
+                                              .withMaxBands((byte) 0)
+                                              .withMaxColors((byte) 0)
+                                              .build());
+        meterStore.storeMeterFeatures(vnet2.id(),
+                                      DefaultMeterFeatures.builder().forDevice(VDID2)
+                                              .withMaxMeters(10)
+                                              .withBandTypes(new HashSet<>())
+                                              .withUnits(new HashSet<>())
+                                              .hasBurst(false)
+                                              .hasStats(false)
+                                              .withMaxBands((byte) 0)
+                                              .withMaxColors((byte) 0)
+                                              .build());
+    }
+
+    private class TestProvider
+            extends AbstractVirtualProvider
+            implements VirtualMeterProvider {
+
+        protected TestProvider() {
+            super(PID);
+        }
+
+        @Override
+        public void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+                                          MeterOperations meterOps) {
+
+        }
+
+        @Override
+        public void performMeterOperation(NetworkId networkId, DeviceId deviceId,
+                                          MeterOperation meterOp) {
+            meters.put(meterOp.meter().id(), meterOp.meter());
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java
new file mode 100644
index 0000000..66ccb73
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerTest.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.TestApplicationId;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkFlowRuleStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkPacketStore;
+import org.onosproject.incubator.net.virtual.VirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.impl.provider.VirtualProviderManager;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualFlowRuleProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualProviderRegistryService;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowObjectiveStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualFlowRuleStore;
+import org.onosproject.incubator.net.virtual.store.impl.SimpleVirtualPacketStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.FlowObjectiveServiceAdapter;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.*;
+import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
+import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
+import static org.onosproject.net.packet.PacketPriority.CONTROL;
+import static org.onosproject.net.packet.PacketPriority.REACTIVE;
+
+/**
+ * Junit tests for VirtualNetworkPacketManager using SimpleVirtualPacketStore.
+ */
+public class VirtualNetworkPacketManagerTest extends VirtualNetworkTestUtil {
+
+    private static final int PROCESSOR_PRIORITY = 1;
+
+    protected VirtualNetworkManager manager;
+    protected DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private CoreService coreService = new TestCoreService();
+    protected TestServiceDirectory testDirectory;
+    private EventDeliveryService eventDeliveryService;
+    private VirtualProviderManager providerRegistryService;
+
+    private VirtualNetwork vnet1;
+    private VirtualNetwork vnet2;
+
+    private VirtualPacketProvider provider = new TestPacketProvider();
+    protected VirtualNetworkPacketStore packetStore = new SimpleVirtualPacketStore();
+
+    protected VirtualNetworkPacketManager packetManager1;
+    private VirtualNetworkPacketManager packetManager2;
+
+    private ApplicationId appId = new TestApplicationId("VirtualPacketManagerTest");
+
+    private VirtualFlowRuleProvider flowRuleProvider = new TestFlowRuleProvider();
+    private SimpleVirtualFlowRuleStore flowRuleStore;
+    private SimpleVirtualFlowObjectiveStore flowObjectiveStore;
+    protected StorageService storageService = new TestStorageService();
+
+    @Before
+    public void setUp() throws TestUtils.TestUtilsException {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", storageService);
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        flowObjectiveStore = new SimpleVirtualFlowObjectiveStore();
+        TestUtils.setField(flowObjectiveStore, "storageService", storageService);
+        flowObjectiveStore.activate();
+        flowRuleStore = new SimpleVirtualFlowRuleStore();
+        flowRuleStore.activate();
+
+        providerRegistryService = new VirtualProviderManager();
+        providerRegistryService.registerProvider(provider);
+        providerRegistryService.registerProvider(flowRuleProvider);
+
+        testDirectory = new TestServiceDirectory()
+                .add(VirtualNetworkStore.class, virtualNetworkManagerStore)
+                .add(CoreService.class, coreService)
+                .add(VirtualProviderRegistryService.class, providerRegistryService)
+                .add(EventDeliveryService.class, eventDeliveryService)
+                .add(ClusterService.class, new ClusterServiceAdapter())
+                .add(VirtualNetworkFlowRuleStore.class, flowRuleStore)
+                .add(VirtualNetworkFlowObjectiveStore.class, flowObjectiveStore)
+                .add(VirtualNetworkPacketStore.class, packetStore);
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        eventDeliveryService = new TestEventDispatcher();
+        NetTestTools.injectEventDispatcher(manager, eventDeliveryService);
+
+        manager.activate();
+
+        vnet1 = VirtualNetworkTestUtil.setupVirtualNetworkTopology(manager, TID1);
+        vnet2 = VirtualNetworkTestUtil.setupVirtualNetworkTopology(manager, TID2);
+
+        packetManager1 = new VirtualNetworkPacketManager(manager, vnet1.id());
+        packetManager2 = new VirtualNetworkPacketManager(manager, vnet2.id());
+    }
+
+    /**
+     * Tests the correct usage of addProcessor() for a outbound packet.
+     */
+    @Test
+    public void addProcessorTest() {
+        PacketProcessor testProcessor = new TestProcessor();
+        packetManager1.addProcessor(testProcessor, PROCESSOR_PRIORITY);
+
+        assertEquals("1 processor expected", 1,
+                    packetManager1.getProcessors().size());
+        assertEquals("0 processor expected", 0,
+                     packetManager2.getProcessors().size());
+
+        assertEquals("not equal packet processor", testProcessor,
+                     packetManager1.getProcessors().get(0).processor());
+        assertEquals("not equal packet processor priority", PROCESSOR_PRIORITY,
+                     packetManager1.getProcessors().get(0).priority());
+    }
+
+    /**
+     * Tests the correct usage of addProcessor() for a outbound packet.
+     */
+    @Test
+    public void removeProcessorTest() {
+        PacketProcessor testProcessor = new TestProcessor();
+        packetManager1.addProcessor(testProcessor, PROCESSOR_PRIORITY);
+
+        assertEquals("1 processor expected", 1,
+                     packetManager1.getProcessors().size());
+        assertEquals("0 processor expected", 0,
+                     packetManager2.getProcessors().size());
+
+        packetManager1.removeProcessor(testProcessor);
+
+        assertEquals("0 processor expected", 0,
+                     packetManager1.getProcessors().size());
+        assertEquals("0 processor expected", 0,
+                     packetManager2.getProcessors().size());
+    }
+
+    /**
+     * Tests the correct usage of emit() for a outbound packet.
+     */
+    @Test
+    public void emitTest() {
+        OutboundPacket packet =
+                new DefaultOutboundPacket(VDID1, DefaultTrafficTreatment.emptyTreatment(), ByteBuffer.allocate(5));
+        packetManager1.emit(packet);
+        assertEquals("Packet not emitted correctly", packet, emittedPacket);
+    }
+
+    /**
+     * Tests the addition and removal of packet requests for a device.
+     *
+     * @throws TestUtils.TestUtilsException
+     */
+    @Test
+    public void requestAndCancelPacketsForDeviceTest() throws TestUtils.TestUtilsException {
+        TestFlowObjectiveService testFlowObjectiveService = new TestFlowObjectiveService();
+        TestUtils.setField(packetManager1, "objectiveService", testFlowObjectiveService);
+        TrafficSelector ts = DefaultTrafficSelector.emptySelector();
+        Optional<DeviceId> optionalDeviceId = Optional.of(VDID3);
+
+        // add first request
+        packetManager1.requestPackets(ts, CONTROL, appId, optionalDeviceId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectiveForDevice(VDID3, ts, CONTROL, ADD);
+
+        // add same request as first
+        packetManager1.requestPackets(ts, CONTROL, appId, optionalDeviceId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectiveForDevice(VDID3, ts, CONTROL, ADD);
+
+        // add second request
+        packetManager1.requestPackets(ts, REACTIVE, appId, optionalDeviceId);
+        assertEquals("2 packets expected", 2, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectiveForDevice(VDID3, ts, REACTIVE, ADD);
+
+        // cancel second request
+        packetManager1.cancelPackets(ts, REACTIVE, appId, optionalDeviceId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectiveForDevice(VDID3, ts, REACTIVE, REMOVE);
+
+        // cancel second request again
+        packetManager1.cancelPackets(ts, REACTIVE, appId, optionalDeviceId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectiveForDevice(VDID3, ts, REACTIVE, REMOVE);
+
+        // cancel first request
+        packetManager1.cancelPackets(ts, CONTROL, appId, optionalDeviceId);
+        assertEquals("0 packet expected", 0, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectiveForDevice(VDID3, ts, CONTROL, REMOVE);
+    }
+
+    /**
+     * Tests the addition and removal of packet requests for all devices in a virtual
+     * network.
+     *
+     * @throws TestUtils.TestUtilsException
+     */
+    @Test
+    public void requestAndCancelPacketsForVnetTest() throws TestUtils.TestUtilsException {
+        TestFlowObjectiveService testFlowObjectiveService = new TestFlowObjectiveService();
+        TestUtils.setField(packetManager1, "objectiveService", testFlowObjectiveService);
+        TrafficSelector ts = DefaultTrafficSelector.emptySelector();
+        Set<VirtualDevice> vnet1Devices = manager.getVirtualDevices(vnet1.id());
+
+        // add first request
+        packetManager1.requestPackets(ts, CONTROL, appId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectives(vnet1Devices, ts, CONTROL, ADD);
+
+        // add same request as first
+        packetManager1.requestPackets(ts, CONTROL, appId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectives(vnet1Devices, ts, CONTROL, ADD);
+
+        // add second request
+        packetManager1.requestPackets(ts, REACTIVE, appId);
+        assertEquals("2 packets expected", 2, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectives(vnet1Devices, ts, REACTIVE, ADD);
+
+        // cancel second request
+        packetManager1.cancelPackets(ts, REACTIVE, appId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectives(vnet1Devices, ts, REACTIVE, REMOVE);
+
+        // cancel second request again
+        packetManager1.cancelPackets(ts, REACTIVE, appId);
+        assertEquals("1 packet expected", 1, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectives(vnet1Devices, ts, REACTIVE, REMOVE);
+
+        // cancel first request
+        packetManager1.cancelPackets(ts, CONTROL, appId);
+        assertEquals("0 packet expected", 0, packetManager1.getRequests().size());
+        testFlowObjectiveService.validateObjectives(vnet1Devices, ts, CONTROL, REMOVE);
+    }
+
+    protected OutboundPacket emittedPacket = null;
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return appId;
+        }
+    }
+
+    private class TestPacketProvider extends AbstractVirtualProvider
+            implements VirtualPacketProvider {
+
+        /**
+         * Creates a provider with the supplied identifier.
+         */
+        protected TestPacketProvider() {
+            super(new ProviderId("test-packet",
+                                 "org.onosproject.virtual.test-packet"));
+        }
+
+        @Override
+        public void emit(NetworkId networkId, OutboundPacket packet) {
+            emittedPacket = packet;
+        }
+
+    }
+
+    private class TestProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+
+        }
+    }
+
+    private class TestFlowObjectiveService extends FlowObjectiveServiceAdapter {
+        // track objectives received for each device
+        private final Map<DeviceId, Set<ForwardingObjective>> deviceFwdObjs = new HashMap<>();
+
+        @Override
+        public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) {
+            deviceFwdObjs.compute(deviceId, (deviceId1, forwardingObjectives) -> {
+                        if (forwardingObjectives == null) {
+                            return Sets.newHashSet(forwardingObjective);
+                        }
+                        forwardingObjectives.add(forwardingObjective);
+                        return forwardingObjectives;
+                    }
+            );
+        }
+
+        private void validateObjectives(Set<VirtualDevice> vdevs, TrafficSelector ts,
+                                    PacketPriority pp, Objective.Operation op) {
+            assertNotNull("set of devices must not be null", vdevs);
+            for (VirtualDevice vdev: vdevs) {
+                assertTrue("Forwarding objective must exist for device " + vdev.id(),
+                           deviceHasObjective(vdev.id(), ts, pp, op));
+            }
+        }
+
+        private void validateObjectiveForDevice(DeviceId deviceId, TrafficSelector ts,
+                                    PacketPriority pp, Objective.Operation op) {
+            assertNotNull("deviceId must not be null", deviceId);
+            assertTrue("Forwarding objective must exist for device " + deviceId,
+                           deviceHasObjective(deviceId, ts, pp, op));
+        }
+
+        private boolean deviceHasObjective(DeviceId deviceId, TrafficSelector ts,
+                                   PacketPriority pp, Objective.Operation op) {
+            Set<ForwardingObjective> fos = deviceFwdObjs.get(deviceId);
+            if (fos != null) {
+                for (ForwardingObjective fo: fos) {
+                    if (fo.selector().equals(ts)
+                            && fo.priority() == pp.priorityValue()
+                            && fo.op().equals(op)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    private class TestFlowRuleProvider extends AbstractVirtualProvider
+            implements VirtualFlowRuleProvider {
+
+        protected TestFlowRuleProvider() {
+            super(new ProviderId("test", "org.onosproject.virtual.testprovider"));
+        }
+
+        @Override
+        public void applyFlowRule(NetworkId networkId, FlowRule... flowRules) {
+
+        }
+
+        @Override
+        public void removeFlowRule(NetworkId networkId, FlowRule... flowRules) {
+
+        }
+
+        @Override
+        public void executeBatch(NetworkId networkId, FlowRuleBatchOperation batch) {
+
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerWithDistStoreTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerWithDistStoreTest.java
new file mode 100644
index 0000000..914d709
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPacketManagerWithDistStoreTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.ComponentContextAdapter;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualPacketStore;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationServiceAdapter;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+import static org.junit.Assert.assertNull;
+
+/**
+ * Junit tests for VirtualNetworkPacketManager using DistributedVirtualPacketStore..
+ * This test class extends VirtualNetworkPacketManagerTest - all the tests defined in
+ * VirtualNetworkPacketManagerTest will run using DistributedVirtualPacketStore.
+ */
+public class VirtualNetworkPacketManagerWithDistStoreTest extends VirtualNetworkPacketManagerTest {
+
+    private DistributedVirtualPacketStore distStore;
+    private ClusterService clusterService = new ClusterServiceAdapter();
+
+    @Before
+    public void setUp() throws TestUtils.TestUtilsException {
+        setUpDistPacketStore();
+        super.setUp();
+        TestUtils.setField(packetManager1, "storageService", storageService);
+    }
+
+    private void setUpDistPacketStore() throws TestUtils.TestUtilsException {
+        distStore = new DistributedVirtualPacketStore();
+        TestUtils.setField(distStore, "cfgService", new ComponentConfigAdapter());
+        TestUtils.setField(distStore, "storageService", storageService);
+        TestUtils.setField(distStore, "clusterService", clusterService);
+        TestUtils.setField(distStore, "communicationService", new TestClusterCommunicationService());
+        TestUtils.setField(distStore, "mastershipService", new TestMastershipService());
+
+        distStore.activate(new ComponentContextAdapter());
+        packetStore = distStore; // super.setUp() will cause Distributed store to be used.
+    }
+
+    @After
+    public void tearDown() {
+        distStore.deactivate();
+    }
+
+    @Override
+    @Test
+    @Ignore("Ignore until there is MastershipService support for virtual devices")
+    public void emitTest() {
+        super.emitTest();
+    }
+
+    /**
+     * Tests the correct usage of emit() for a outbound packet - master of packet's
+     * sendThrough is not local node.
+     */
+    @Test
+    @Ignore("Ignore until there is MastershipService support for virtual devices")
+    public void emit2Test() {
+        OutboundPacket packet =
+                new DefaultOutboundPacket(VDID2, DefaultTrafficTreatment.emptyTreatment(), ByteBuffer.allocate(5));
+        packetManager1.emit(packet);
+        assertNull("Packet should not have been emmitted", emittedPacket);
+    }
+
+    private final class TestMastershipService extends MastershipServiceAdapter {
+        @Override
+        public NodeId getMasterFor(DeviceId deviceId) {
+            if (VDID1.equals(deviceId)) {
+                return clusterService.getLocalNode().id();
+            }
+            return new NodeId("abc");
+        }
+    }
+
+    private final class TestClusterCommunicationService extends ClusterCommunicationServiceAdapter {
+        @Override
+        public <M> CompletableFuture<Void> unicast(M message, MessageSubject subject,
+                                                   Function<M, byte[]> encoder, NodeId toNodeId) {
+            return new CompletableFuture<>();
+        }
+    }
+
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManagerTest.java
new file mode 100644
index 0000000..b8db7ea
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkPathManagerTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.graph.ScalarWeight;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.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.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.LinkWeigherAdapter;
+import org.onosproject.net.topology.PathService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Junit tests for VirtualNetworkPathService.
+ */
+public class VirtualNetworkPathManagerTest extends TestDeviceParams {
+    private final String tenantIdValue1 = "TENANT_ID1";
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+
+    private TestServiceDirectory testDirectory;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        CoreService coreService = new TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+    }
+
+    /**
+     * Sets up an empty virtual network (no devices, links).
+     *
+     * @return virtual network
+     */
+    private VirtualNetwork setupEmptyVnet() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        return manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+    }
+
+    /**
+     * Creates a virtual network for further testing.
+     *
+     * @return virtual network
+     */
+    private VirtualNetwork setupVnet() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        VirtualDevice virtualDevice3 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID3);
+        VirtualDevice virtualDevice4 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID4);
+
+        ConnectPoint cp11 = createConnectPointAndVirtualPort(virtualNetwork, virtualDevice1, 1);
+        ConnectPoint cp12 = createConnectPointAndVirtualPort(virtualNetwork, virtualDevice1, 2);
+        ConnectPoint cp23 = createConnectPointAndVirtualPort(virtualNetwork, virtualDevice2, 3);
+        ConnectPoint cp24 = createConnectPointAndVirtualPort(virtualNetwork, virtualDevice2, 4);
+        ConnectPoint cp35 = createConnectPointAndVirtualPort(virtualNetwork, virtualDevice3, 5);
+        ConnectPoint cp36 = createConnectPointAndVirtualPort(virtualNetwork, virtualDevice3, 6);
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), cp11, cp23);
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), cp23, cp11);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link3 = manager.createVirtualLink(virtualNetwork.id(), cp24, cp35);
+        virtualNetworkManagerStore.updateLink(link3, link3.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link4 = manager.createVirtualLink(virtualNetwork.id(), cp35, cp24);
+        virtualNetworkManagerStore.updateLink(link4, link4.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link5 = manager.createVirtualLink(virtualNetwork.id(), cp12, cp36);
+        virtualNetworkManagerStore.updateLink(link5, link5.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link6 = manager.createVirtualLink(virtualNetwork.id(), cp36, cp12);
+        virtualNetworkManagerStore.updateLink(link6, link6.tunnelId(), Link.State.ACTIVE);
+
+        return virtualNetwork;
+    }
+
+    /**
+     * Creates a connect point and related virtual port.
+     *
+     * @param vnet virtual network
+     * @param vDev virtual device
+     * @param portNumber port number
+     * @return connect point
+     */
+    private ConnectPoint createConnectPointAndVirtualPort(
+            VirtualNetwork vnet, VirtualDevice vDev, long portNumber) {
+        ConnectPoint cp = new ConnectPoint(vDev.id(), PortNumber.portNumber(portNumber));
+        manager.createVirtualPort(vnet.id(), cp.deviceId(), cp.port(),
+                                  new ConnectPoint(vDev.id(), cp.port()));
+        return cp;
+    }
+
+    /**
+     * Tests getPaths(), getDisjointPaths()
+     * on a non-empty virtual network.
+     */
+    @Test
+    public void testGetPathsOnNonEmptyVnet() {
+        VirtualNetwork vnet = setupVnet();
+        PathService pathService = manager.get(vnet.id(), PathService.class);
+
+        // src and dest are in vnet and are connected by a virtual link
+        Set<Path> paths = pathService.getPaths(DID1, DID3);
+        validatePaths(paths, 1, 1, DID1, DID3, 1.0);
+
+        LinkWeigher linkWeight = new LinkWeigherAdapter(2.0);
+        paths = pathService.getPaths(DID1, DID3, linkWeight);
+        validatePaths(paths, 1, 1, DID1, DID3, 2.0);
+
+        Set<DisjointPath> disjointPaths = pathService.getDisjointPaths(DID1, DID3);
+        validatePaths(disjointPaths, 1, 1, DID1, DID3, 1.0);
+
+        disjointPaths = pathService.getDisjointPaths(DID1, DID3, linkWeight);
+        validatePaths(disjointPaths, 1, 1, DID1, DID3, 2.0);
+
+        // src and dest are in vnet but are not connected
+        paths = pathService.getPaths(DID4, DID3);
+        assertEquals("incorrect path count", 0, paths.size());
+
+        disjointPaths = pathService.getDisjointPaths(DID4, DID3);
+        assertEquals("incorrect path count", 0, disjointPaths.size());
+
+        // src is in vnet, but dest is not in vnet.
+        DeviceId nonExistentDeviceId = DeviceId.deviceId("nonExistentDevice");
+        paths = pathService.getPaths(DID2, nonExistentDeviceId);
+        assertEquals("incorrect path count", 0, paths.size());
+
+        disjointPaths = pathService.getDisjointPaths(DID2, nonExistentDeviceId);
+        assertEquals("incorrect path count", 0, disjointPaths.size());
+    }
+
+    /**
+     * Tests getPaths(), getDisjointPaths()
+     * on an empty virtual network.
+     */
+    @Test
+    public void testGetPathsOnEmptyVnet() {
+        VirtualNetwork vnet = setupEmptyVnet();
+        PathService pathService = manager.get(vnet.id(), PathService.class);
+
+        Set<Path> paths = pathService.getPaths(DID1, DID3);
+        assertEquals("incorrect path count", 0, paths.size());
+
+        Set<DisjointPath> disjointPaths = pathService.getDisjointPaths(DID1, DID3);
+        assertEquals("incorrect path count", 0, disjointPaths.size());
+    }
+
+    /**
+     * Tests getPaths() using a null source device on an empty virtual network.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPathsWithNullSrc() {
+        VirtualNetwork vnet = setupEmptyVnet();
+        PathService pathService = manager.get(vnet.id(), PathService.class);
+        pathService.getPaths(null, DID3);
+    }
+
+    /**
+     * Tests getPaths() using a null destination device on a non-empty virtual network.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPathsWithNullDest() {
+        VirtualNetwork vnet = setupVnet();
+        PathService pathService = manager.get(vnet.id(), PathService.class);
+        pathService.getPaths(DID1, null);
+    }
+
+
+    // Makes sure the set of paths meets basic expectations.
+    private void validatePaths(Set<? extends Path> paths, int count, int length,
+                               ElementId src, ElementId dst, double cost) {
+        assertEquals("incorrect path count", count, paths.size());
+        for (Path path : paths) {
+            assertEquals("incorrect length", length, path.links().size());
+            assertEquals("incorrect source", src, path.src().elementId());
+            assertEquals("incorrect destination", dst, path.dst().elementId());
+            assertEquals("incorrect cost", ScalarWeight.toWeight(cost), path.weight());
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTestUtil.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTestUtil.java
new file mode 100644
index 0000000..d1b7274
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTestUtil.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.onosproject.net.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.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+
+import static org.onosproject.net.DeviceId.deviceId;
+
+public class VirtualNetworkTestUtil extends TestDeviceParams {
+
+    protected static final TenantId TID1 = TenantId.tenantId("tid1");
+    protected static final TenantId TID2 = TenantId.tenantId("tid2");
+
+    protected static final DeviceId VDID1 = deviceId("of:foo_v");
+    protected static final DeviceId VDID2 = deviceId("of:bar_v");
+    protected static final DeviceId VDID3 = deviceId("of:who_v");
+    protected static final DeviceId VDID4 = deviceId("of:what_v");
+
+    protected static final DeviceId PHYDID1 = deviceId("physical:1");
+    protected static final DeviceId PHYDID2 = deviceId("physical:2");
+    protected static final DeviceId PHYDID3 = deviceId("physical:3");
+    protected static final DeviceId PHYDID4 = deviceId("physical:4");
+
+    /**
+     * Method to create the virtual network for further testing.
+     *
+     * @return virtual network
+     */
+    public static VirtualNetwork setupVirtualNetworkTopology(VirtualNetworkManager manager,
+                                                             TenantId tenantId) {
+        manager.registerTenantId(tenantId);
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(tenantId);
+
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), VDID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), VDID2);
+        VirtualDevice virtualDevice3 =
+                manager.createVirtualDevice(virtualNetwork.id(), VDID3);
+        VirtualDevice virtualDevice4 =
+                manager.createVirtualDevice(virtualNetwork.id(), VDID4);
+
+        ConnectPoint vcp1 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(1));
+        ConnectPoint cp1 = new ConnectPoint(DID1, PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), vcp1.deviceId(), vcp1.port(), cp1);
+
+        ConnectPoint vcp2 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(2));
+        ConnectPoint cp2 = new ConnectPoint(DID1, PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), vcp2.deviceId(), vcp2.port(), cp2);
+
+        ConnectPoint vcp3 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(3));
+        ConnectPoint cp3 = new ConnectPoint(DID2, PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), vcp3.deviceId(), vcp3.port(), cp3);
+
+        ConnectPoint vcp4 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(4));
+        ConnectPoint cp4 = new ConnectPoint(DID2, PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), vcp4.deviceId(), vcp4.port(), cp4);
+
+        ConnectPoint vcp5 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(5));
+        ConnectPoint cp5 = new ConnectPoint(DID3, PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), vcp5.deviceId(), vcp5.port(), cp5);
+
+        ConnectPoint vcp6 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(6));
+        ConnectPoint cp6 = new ConnectPoint(DID3, PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), vcp6.deviceId(), vcp6.port(), cp6);
+
+        DistributedVirtualNetworkStore virtualNetworkManagerStore =
+                (DistributedVirtualNetworkStore) manager.store;
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), vcp1, vcp3);
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), vcp3, vcp1);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link3 = manager.createVirtualLink(virtualNetwork.id(), vcp4, vcp5);
+        virtualNetworkManagerStore.updateLink(link3, link3.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link4 = manager.createVirtualLink(virtualNetwork.id(), vcp5, vcp4);
+        virtualNetworkManagerStore.updateLink(link4, link4.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link5 = manager.createVirtualLink(virtualNetwork.id(), vcp2, vcp6);
+        virtualNetworkManagerStore.updateLink(link5, link5.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link6 = manager.createVirtualLink(virtualNetwork.id(), vcp6, vcp2);
+        virtualNetworkManagerStore.updateLink(link6, link6.tunnelId(), Link.State.ACTIVE);
+
+        return virtualNetwork;
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManagerTest.java
new file mode 100644
index 0000000..8fb7904
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkTopologyManagerTest.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.graph.ScalarWeight;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.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.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DisjointPath;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.LinkWeigherAdapter;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Junit tests for VirtualNetworkTopologyService.
+ */
+public class VirtualNetworkTopologyManagerTest extends TestDeviceParams {
+
+    private final String tenantIdValue1 = "TENANT_ID1";
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private CoreService coreService;
+    private TestServiceDirectory testDirectory;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+        coreService = new VirtualNetworkTopologyManagerTest.TestCoreService();
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        manager.store = virtualNetworkManagerStore;
+        manager.coreService = coreService;
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+    }
+
+    @After
+    public void tearDown() {
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+    }
+
+    /**
+     * Method to create the virtual network for further testing.
+     *
+     * @return virtual network
+     */
+    private VirtualNetwork setupVirtualNetworkTopology() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        VirtualDevice virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        VirtualDevice virtualDevice3 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID3);
+        VirtualDevice virtualDevice4 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID4);
+
+        ConnectPoint cp1 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), cp1.deviceId(), cp1.port(), cp1);
+
+        ConnectPoint cp2 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), cp2.deviceId(), cp2.port(), cp2);
+
+        ConnectPoint cp3 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(3));
+        manager.createVirtualPort(virtualNetwork.id(), cp3.deviceId(), cp3.port(), cp3);
+
+        ConnectPoint cp4 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(4));
+        manager.createVirtualPort(virtualNetwork.id(), cp4.deviceId(), cp4.port(), cp4);
+
+        ConnectPoint cp5 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(5));
+        manager.createVirtualPort(virtualNetwork.id(), cp5.deviceId(), cp5.port(), cp5);
+
+        ConnectPoint cp6 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(6));
+        manager.createVirtualPort(virtualNetwork.id(), cp6.deviceId(), cp6.port(), cp6);
+
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), cp1, cp3);
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), cp3, cp1);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link3 = manager.createVirtualLink(virtualNetwork.id(), cp4, cp5);
+        virtualNetworkManagerStore.updateLink(link3, link3.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link4 = manager.createVirtualLink(virtualNetwork.id(), cp5, cp4);
+        virtualNetworkManagerStore.updateLink(link4, link4.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link5 = manager.createVirtualLink(virtualNetwork.id(), cp2, cp6);
+        virtualNetworkManagerStore.updateLink(link5, link5.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link6 = manager.createVirtualLink(virtualNetwork.id(), cp6, cp2);
+        virtualNetworkManagerStore.updateLink(link6, link6.tunnelId(), Link.State.ACTIVE);
+
+        return virtualNetwork;
+    }
+
+    /**
+     * Tests the currentTopology() method.
+     */
+    @Test
+    public void testCurrentTopology() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+        assertNotNull("The topology should not be null.", topology);
+    }
+
+    /**
+     * Test isLatest() method using a null topology.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsLatestByNullTopology() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+
+        // test the isLatest() method with a null topology.
+        topologyService.isLatest(null);
+    }
+
+    /**
+     * Test isLatest() method.
+     */
+    @Test
+    public void testIsLatest() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        // test the isLatest() method.
+        assertTrue("This should be latest topology", topologyService.isLatest(topology));
+
+        VirtualDevice srcVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the isLatest() method where a new device has been added to the current topology.
+        assertFalse("This should not be latest topology", topologyService.isLatest(topology));
+
+        topology = topologyService.currentTopology();
+        ConnectPoint src = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), src.deviceId(), src.port(),
+                                  new ConnectPoint(srcVirtualDevice.id(), src.port()));
+
+        ConnectPoint dst = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), dst.deviceId(), dst.port(),
+                                  new ConnectPoint(dstVirtualDevice.id(), dst.port()));
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), src, dst);
+
+        // test the isLatest() method where a new link has been added to the current topology.
+        assertFalse("This should not be latest topology", topologyService.isLatest(topology));
+    }
+
+    /**
+     * Test getGraph() method.
+     */
+    @Test
+    public void testGetGraph() {
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        // test the getGraph() method.
+        assertNotNull("The graph should not be null.", topologyService.getGraph(topology));
+    }
+
+    /**
+     * Test getClusters() method.
+     */
+    @Test
+    public void testGetClusters() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+
+        Topology topology = topologyService.currentTopology();
+
+        // test the getClusters() method.
+        assertNotNull("The clusters should not be null.", topologyService.getClusters(topology));
+        assertEquals("The clusters size did not match.", 2, topologyService.getClusters(topology).size());
+    }
+
+    /**
+     * Test getCluster() method using a null cluster identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetClusterUsingNullClusterId() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        Set<TopologyCluster> clusters = topologyService.getClusters(topology);
+        TopologyCluster cluster = clusters.stream().findFirst().get();
+
+        // test the getCluster() method with a null cluster identifier
+        TopologyCluster cluster1 = topologyService.getCluster(topology, null);
+    }
+
+    /**
+     * Test getCluster() method.
+     */
+    @Test
+    public void testGetCluster() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        Set<TopologyCluster> clusters = topologyService.getClusters(topology);
+        assertNotNull("The clusters should not be null.", clusters);
+        assertEquals("The clusters size did not match.", 2, clusters.size());
+
+        // test the getCluster() method.
+        TopologyCluster cluster = clusters.stream().findFirst().get();
+        assertNotNull("The cluster should not be null.", cluster);
+        TopologyCluster cluster1 = topologyService.getCluster(topology, cluster.id());
+        assertNotNull("The cluster should not be null.", cluster1);
+        assertEquals("The cluster ID did not match.", cluster.id(), cluster1.id());
+    }
+
+    /**
+     * Test getClusterDevices() methods with a null cluster.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetClusterDevicesUsingNullCluster() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+        Set<TopologyCluster> clusters = topologyService.getClusters(topology);
+
+        // test the getClusterDevices() method using a null cluster.
+        Object[] objects = clusters.stream().toArray();
+        assertNotNull("The cluster should not be null.", objects);
+        Set<DeviceId> clusterDevices = topologyService.getClusterDevices(topology, null);
+    }
+
+    /**
+     * Test getClusterLinks() methods with a null cluster.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetClusterLinksUsingNullCluster() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+        Set<TopologyCluster> clusters = topologyService.getClusters(topology);
+
+        // test the getClusterLinks() method using a null cluster.
+        Object[] objects = clusters.stream().toArray();
+        assertNotNull("The cluster should not be null.", objects);
+        Set<Link> clusterLinks = topologyService.getClusterLinks(topology, null);
+    }
+
+    /**
+     * Test getClusterDevices() and getClusterLinks() methods.
+     */
+    @Test
+    public void testGetClusterDevicesLinks() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        Set<TopologyCluster> clusters = topologyService.getClusters(topology);
+        assertNotNull("The clusters should not be null.", clusters);
+        assertEquals("The clusters size did not match.", 2, clusters.size());
+
+        // test the getClusterDevices() method.
+        Object[] objects = clusters.stream().toArray();
+        assertNotNull("The cluster should not be null.", objects);
+        Set<DeviceId> clusterDevices = topologyService.getClusterDevices(topology, (TopologyCluster) objects[0]);
+        assertNotNull("The devices should not be null.", clusterDevices);
+        assertEquals("The devices size did not match.", 3, clusterDevices.size());
+        Set<DeviceId> clusterDevices1 = topologyService.getClusterDevices(topology, (TopologyCluster) objects[1]);
+        assertNotNull("The devices should not be null.", clusterDevices1);
+        assertEquals("The devices size did not match.", 1, clusterDevices1.size());
+
+        // test the getClusterLinks() method.
+        Set<Link> clusterLinks = topologyService.getClusterLinks(topology, (TopologyCluster) objects[0]);
+        assertNotNull("The links should not be null.", clusterLinks);
+        assertEquals("The links size did not match.", 6, clusterLinks.size());
+        Set<Link> clusterLinks1 = topologyService.getClusterLinks(topology, (TopologyCluster) objects[1]);
+        assertNotNull("The links should not be null.", clusterLinks1);
+        assertEquals("The links size did not match.", 0, clusterLinks1.size());
+    }
+
+    /**
+     * Test getPaths() method using a null src device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPathsUsingNullSrcDeviceId() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getPaths() method using a null src device identifier.
+        Set<Path> paths = topologyService.getPaths(topology, null, dstVirtualDevice.id());
+    }
+
+    /**
+     * Test getPaths() method using a null dst device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPathsUsingNullDstDeviceId() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getPaths() method using a null dst device identifier.
+        Set<Path> paths = topologyService.getPaths(topology, srcVirtualDevice.id(), null);
+    }
+
+    /**
+     * Test getPaths() method using a null weight.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetPathsUsingNullWeight() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getPaths() method using a null weight.
+        Set<Path> paths = topologyService.getPaths(topology, srcVirtualDevice.id(),
+                dstVirtualDevice.id(), (LinkWeigher) null);
+    }
+
+    /**
+     * Test getPaths() and getPaths() by weight methods.
+     */
+    @Test
+    public void testGetPaths() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getPaths() method.
+        Set<Path> paths = topologyService.getPaths(topology, srcVirtualDevice.id(), dstVirtualDevice.id());
+        assertNotNull("The paths should not be null.", paths);
+        assertEquals("The paths size did not match.", 1, paths.size());
+
+        // test the getPaths() by weight method.
+        LinkWeigher weight = new LinkWeigherAdapter(1.0);
+        Set<Path> paths1 = topologyService.getPaths(topology, srcVirtualDevice.id(), dstVirtualDevice.id(), weight);
+        assertNotNull("The paths should not be null.", paths1);
+        assertEquals("The paths size did not match.", 1, paths1.size());
+        Path path = paths1.iterator().next();
+        assertEquals("wrong path length", 1, path.links().size());
+        assertEquals("wrong path cost", ScalarWeight.toWeight(1.0), path.weight());
+    }
+
+    /**
+     * Test getDisjointPaths() methods using a null src device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDisjointPathsUsingNullSrcDeviceId() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getDisjointPaths() method using a null src device identifier.
+        Set<DisjointPath> paths = topologyService.getDisjointPaths(topology, null, dstVirtualDevice.id());
+    }
+
+    /**
+     * Test getDisjointPaths() methods using a null dst device identifier.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDisjointPathsUsingNullDstDeviceId() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getDisjointPaths() method using a null dst device identifier.
+        Set<DisjointPath> paths = topologyService.getDisjointPaths(topology, srcVirtualDevice.id(), null);
+    }
+
+    /**
+     * Test getDisjointPaths() methods using a null weight.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDisjointPathsUsingNullWeight() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getDisjointPaths() method using a null weight.
+        Set<DisjointPath> paths = topologyService.getDisjointPaths(topology, srcVirtualDevice.id(),
+                                                                   dstVirtualDevice.id(), (LinkWeigher) null);
+    }
+
+    /**
+     * Test getDisjointPaths() methods using a null risk profile.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testGetDisjointPathsUsingNullRiskProfile() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getDisjointPaths() method using a null risk profile.
+        Set<DisjointPath> paths = topologyService.getDisjointPaths(topology, srcVirtualDevice.id(),
+                                                                   dstVirtualDevice.id(), (Map<Link, Object>) null);
+    }
+
+    /**
+     * Test getDisjointPaths() methods.
+     */
+    @Test
+    public void testGetDisjointPaths() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID2);
+
+        // test the getDisjointPaths() method.
+        Set<DisjointPath> paths = topologyService.getDisjointPaths(topology, srcVirtualDevice.id(),
+                                                                   dstVirtualDevice.id());
+        assertNotNull("The paths should not be null.", paths);
+        assertEquals("The paths size did not match.", 1, paths.size());
+
+        // test the getDisjointPaths() method using a weight.
+        LinkWeigher weight = new LinkWeigherAdapter(1.0);
+        Set<DisjointPath> paths1 = topologyService.getDisjointPaths(topology, srcVirtualDevice.id(),
+                                                                    dstVirtualDevice.id(), weight);
+        assertNotNull("The paths should not be null.", paths1);
+        assertEquals("The paths size did not match.", 1, paths1.size());
+    }
+
+    /**
+     * Test isInfrastructure() method using a null connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsInfrastructureUsingNullConnectPoint() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        // test the isInfrastructure() method using a null connect point.
+        Boolean isInfrastructure = topologyService.isInfrastructure(topology, null);
+    }
+
+    /**
+     * Test isInfrastructure() method.
+     */
+    @Test
+    public void testIsInfrastructure() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        VirtualDevice dstVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID4);
+        ConnectPoint cp1 = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+        ConnectPoint cp2 = new ConnectPoint(dstVirtualDevice.id(), PortNumber.portNumber(2));
+
+        // test the isInfrastructure() method.
+        Boolean isInfrastructure = topologyService.isInfrastructure(topology, cp1);
+        assertTrue("The connect point should be infrastructure.", isInfrastructure);
+
+        isInfrastructure = topologyService.isInfrastructure(topology, cp2);
+        assertFalse("The connect point should not be infrastructure.", isInfrastructure);
+    }
+
+    /**
+     * Test isBroadcastPoint() method using a null connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsBroadcastUsingNullConnectPoint() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        // test the isInfrastructure() method using a null connect point.
+        Boolean isInfrastructure = topologyService.isBroadcastPoint(topology, null);
+    }
+
+    /**
+     * Test isBroadcastPoint() method.
+     */
+    @Test
+    public void testIsBroadcastPoint() {
+        VirtualNetwork virtualNetwork = setupVirtualNetworkTopology();
+
+        TopologyService topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+
+        VirtualDevice srcVirtualDevice = getVirtualDevice(virtualNetwork.id(), DID1);
+        ConnectPoint cp = new ConnectPoint(srcVirtualDevice.id(), PortNumber.portNumber(1));
+
+        // test the isBroadcastPoint() method.
+        Boolean isBroadcastPoint = topologyService.isBroadcastPoint(topology, cp);
+        assertTrue("The connect point should be a broadcast point.", isBroadcastPoint);
+    }
+
+    /**
+     * Return the virtual device matching the device identifier.
+     *
+     * @param networkId virtual network identifier
+     * @param deviceId  device identifier
+     * @return virtual device
+     */
+    private VirtualDevice getVirtualDevice(NetworkId networkId, DeviceId deviceId) {
+        Optional<VirtualDevice> foundDevice = manager.getVirtualDevices(networkId)
+                .stream()
+                .filter(device -> deviceId.equals(device.id()))
+                .findFirst();
+        if (foundDevice.isPresent()) {
+            return foundDevice.get();
+        }
+        return null;
+    }
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        ApplicationId appId;
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            appId = new DefaultApplicationId(1, name);
+            return appId;
+        }
+
+            @Override
+        public ApplicationId getAppId(String name) {
+            return appId;
+        }
+    }
+
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java
new file mode 100644
index 0000000..8e11e6f
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualFlowRuleProviderTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.graph.ScalarWeight;
+import org.onlab.graph.Weight;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+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.net.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.VirtualNetworkAdminServiceAdapter;
+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.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleServiceAdapter;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyServiceAdapter;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+
+public class DefaultVirtualFlowRuleProviderTest {
+    private static final ProviderId PID = new ProviderId("of", "foo");
+
+    private static final DeviceId DID1 = DeviceId.deviceId("of:001");
+    private static final DeviceId DID2 = DeviceId.deviceId("of:002");
+    private static final PortNumber PORT_NUM1 = PortNumber.portNumber(1);
+    private static final PortNumber PORT_NUM2 = PortNumber.portNumber(2);
+
+    private static final DefaultAnnotations ANNOTATIONS =
+            DefaultAnnotations.builder().set("foo", "bar").build();
+
+    private static final Device DEV1 =
+            new DefaultDevice(PID, DID1, Device.Type.SWITCH, "", "", "", "", null);
+    private static final Device DEV2 =
+            new DefaultDevice(PID, DID2, Device.Type.SWITCH, "", "", "", "", null);
+    private static final Port PORT11 =
+            new DefaultPort(DEV1, PORT_NUM1, true, ANNOTATIONS);
+    private static final Port PORT12 =
+            new DefaultPort(DEV1, PORT_NUM2, true, ANNOTATIONS);
+    private static final Port PORT21 =
+            new DefaultPort(DEV2, PORT_NUM1, true, ANNOTATIONS);
+    private static final Port PORT22 =
+            new DefaultPort(DEV2, PORT_NUM2, true, ANNOTATIONS);
+
+    private static final ConnectPoint CP11 = new ConnectPoint(DID1, PORT_NUM1);
+    private static final ConnectPoint CP12 = new ConnectPoint(DID1, PORT_NUM2);
+    private static final ConnectPoint CP21 = new ConnectPoint(DID2, PORT_NUM1);
+    private static final ConnectPoint CP22 = new ConnectPoint(DID2, PORT_NUM2);
+    private static final Link LINK1 = DefaultLink.builder()
+            .src(CP12).dst(CP21).providerId(PID).type(Link.Type.DIRECT).build();
+
+    private static final NetworkId VNET_ID = NetworkId.networkId(1);
+    private static final DeviceId VDID = DeviceId.deviceId("of:100");
+
+    private static final VirtualNetwork VNET = new DefaultVirtualNetwork(
+            VNET_ID, TenantId.tenantId("t1"));
+    private static final VirtualDevice VDEV =
+            new DefaultVirtualDevice(VNET_ID, VDID);
+    private static final VirtualPort VPORT1 =
+            new DefaultVirtualPort(VNET_ID, VDEV, PORT_NUM1, CP11);
+    private static final VirtualPort VPORT2 =
+            new DefaultVirtualPort(VNET_ID, VDEV, PORT_NUM2, CP22);
+
+    private static final int TIMEOUT = 10;
+
+    protected DefaultVirtualFlowRuleProvider virtualProvider;
+
+    private ApplicationId vAppId;
+
+    @Before
+    public void setUp() {
+        virtualProvider = new DefaultVirtualFlowRuleProvider();
+
+        virtualProvider.deviceService = new TestDeviceService();
+        virtualProvider.coreService = new TestCoreService();
+        virtualProvider.vnService =
+                new TestVirtualNetworkAdminService();
+        virtualProvider.topologyService = new TestTopologyService();
+        virtualProvider.flowRuleService = new TestFlowRuleService();
+        virtualProvider.providerRegistryService = new VirtualProviderManager();
+
+        virtualProvider.activate();
+        vAppId = new TestApplicationId(0, "Virtual App");
+    }
+
+    @After
+    public void tearDown() {
+        virtualProvider.deactivate();
+        virtualProvider.deviceService = null;
+        virtualProvider.coreService = null;
+    }
+
+    @Test
+    public void devirtualizeFlowRuleWithInPort() {
+        TrafficSelector ts = DefaultTrafficSelector.builder()
+                .matchInPort(PORT_NUM1).build();
+        TrafficTreatment tr = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_NUM2).build();
+
+        FlowRule r1 = DefaultFlowRule.builder()
+                .forDevice(VDID)
+                .withSelector(ts)
+                .withTreatment(tr)
+                .withPriority(10)
+                .fromApp(vAppId)
+                .makeTemporary(TIMEOUT)
+                .build();
+
+        virtualProvider.applyFlowRule(VNET_ID, r1);
+
+        assertEquals("2 rules should exist", 2,
+                     virtualProvider.flowRuleService.getFlowRuleCount());
+
+        Set<FlowEntry> phyRules = new HashSet<>();
+        for (FlowEntry i : virtualProvider.flowRuleService.getFlowEntries(DID1)) {
+            phyRules.add(i);
+        }
+        for (FlowEntry i : virtualProvider.flowRuleService.getFlowEntries(DID2)) {
+            phyRules.add(i);
+        }
+
+        FlowRule in = null;
+        FlowRule out = null;
+
+        for (FlowRule rule : phyRules) {
+
+            L2ModificationInstruction i = (L2ModificationInstruction)
+                    rule.treatment().allInstructions().get(0);
+
+            if (i.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) {
+                in = rule;
+            } else {
+                out = rule;
+            }
+
+        }
+
+        assertEquals(DID1, in.deviceId());
+        assertEquals(DID2, out.deviceId());
+    }
+
+    @Test
+    public void devirtualizeFlowRuleWithoutInPort() {
+        TrafficSelector ts = DefaultTrafficSelector.builder().build();
+        TrafficTreatment tr = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_NUM2).build();
+
+        FlowRule r1 = DefaultFlowRule.builder()
+                .forDevice(VDID)
+                .withSelector(ts)
+                .withTreatment(tr)
+                .withPriority(10)
+                .fromApp(vAppId)
+                .makeTemporary(TIMEOUT)
+                .build();
+
+        virtualProvider.applyFlowRule(VNET_ID, r1);
+
+        assertEquals("3 rules should exist", 3,
+                     virtualProvider.flowRuleService.getFlowRuleCount());
+
+        FlowRule inFromDID1 = null;
+        FlowRule inFromDID2 = null;
+        FlowRule out = null;
+
+        Set<FlowEntry> phyRules = new HashSet<>();
+        for (FlowEntry i : virtualProvider.flowRuleService.getFlowEntries(DID1)) {
+            phyRules.add(i);
+        }
+        for (FlowEntry i : virtualProvider.flowRuleService.getFlowEntries(DID2)) {
+            phyRules.add(i);
+        }
+
+        for (FlowRule rule : phyRules) {
+            for (Instruction inst : rule.treatment().allInstructions()) {
+                if (inst.type() == Instruction.Type.L2MODIFICATION) {
+                    L2ModificationInstruction i = (L2ModificationInstruction) inst;
+                    if (i.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) {
+                        inFromDID1 = rule;
+                        break;
+                    } else {
+                        out = rule;
+                        break;
+                    }
+                } else {
+                    inFromDID2 = rule;
+                    break;
+                }
+            }
+        }
+
+        assertEquals(DID1, inFromDID1.deviceId());
+        assertEquals(DID2, inFromDID2.deviceId());
+        assertEquals(DID2, out.deviceId());
+    }
+
+    @Test
+    public void removeVirtualizeFlowRule() {
+        TrafficSelector ts = DefaultTrafficSelector.builder().build();
+        TrafficTreatment tr = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_NUM2).build();
+
+        FlowRule r1 = DefaultFlowRule.builder()
+                .forDevice(VDID)
+                .withSelector(ts)
+                .withTreatment(tr)
+                .withPriority(10)
+                .fromApp(vAppId)
+                .makeTemporary(TIMEOUT)
+                .build();
+
+        virtualProvider.removeFlowRule(VNET_ID, r1);
+
+        assertEquals("0 rules should exist", 0,
+                     virtualProvider.flowRuleService.getFlowRuleCount());
+    }
+
+
+    private static class TestDeviceService extends DeviceServiceAdapter {
+        @Override
+        public int getDeviceCount() {
+            return 2;
+        }
+
+        @Override
+        public Iterable<Device> getDevices() {
+            return ImmutableList.of(DEV1, DEV2);
+        }
+
+        @Override
+        public Iterable<Device> getAvailableDevices() {
+            return getDevices();
+        }
+
+        @Override
+        public Device getDevice(DeviceId deviceId) {
+            return deviceId.equals(DID2) ? DEV2 : DEV1;
+        }
+    }
+
+    private static class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return new TestApplicationId(1, name);
+        }
+    }
+
+    private static class TestApplicationId extends DefaultApplicationId {
+        public TestApplicationId(int id, String name) {
+            super(id, name);
+        }
+    }
+
+    private class TestVirtualNetworkAdminService
+            extends VirtualNetworkAdminServiceAdapter {
+
+        @Override
+        public Set<VirtualDevice> getVirtualDevices(NetworkId networkId) {
+            return ImmutableSet.of(VDEV);
+        }
+
+        @Override
+        public Set<VirtualLink> getVirtualLinks(NetworkId networkId) {
+            return new HashSet<>();
+        }
+
+        @Override
+        public Set<VirtualPort> getVirtualPorts(NetworkId networkId,
+                                                DeviceId deviceId) {
+            return ImmutableSet.of(VPORT1, VPORT2);
+        }
+
+        @Override
+        public ApplicationId getVirtualNetworkApplicationId(NetworkId networkId) {
+            return vAppId;
+        }
+    }
+
+    private static class TestTopologyService extends TopologyServiceAdapter {
+
+        Weight oneHundred = ScalarWeight.toWeight(100);
+        @Override
+        public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
+            DefaultPath path = new DefaultPath(PID, ImmutableList.of(LINK1),
+                                               oneHundred, ANNOTATIONS);
+            return ImmutableSet.of(path);
+        }
+
+        @Override
+        public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
+                                  LinkWeigher weigher) {
+            DefaultPath path = new DefaultPath(PID, ImmutableList.of(LINK1),
+                                               oneHundred, ANNOTATIONS);
+            return ImmutableSet.of(path);
+        }
+
+    }
+
+    private static class TestFlowRuleService extends FlowRuleServiceAdapter {
+        static Set<FlowRule> ruleCollection = new HashSet<>();
+
+        @Override
+        public int getFlowRuleCount() {
+            return ruleCollection.size();
+        }
+
+        @Override
+        public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+            return ruleCollection.stream()
+                    .filter(r -> r.deviceId().equals(deviceId))
+                    .map(DefaultFlowEntry::new)
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public void applyFlowRules(FlowRule... flowRules) {
+            ruleCollection.addAll(Arrays.asList(flowRules));
+        }
+
+        @Override
+        public void removeFlowRules(FlowRule... flowRules) {
+            Set<FlowRule> candidates = new HashSet<>();
+            for (FlowRule rule : flowRules) {
+                ruleCollection.stream()
+                        .filter(r -> r.exactMatch(rule))
+                        .forEach(candidates::add);
+            }
+            ruleCollection.removeAll(candidates);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProviderTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProviderTest.java
new file mode 100644
index 0000000..a17a0db
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/DefaultVirtualPacketProviderTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+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.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualNetwork;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminServiceAdapter;
+import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualPacketProviderService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.DefaultPacketContext;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+
+public class DefaultVirtualPacketProviderTest {
+    private static final String SRC_MAC_ADDR = "00:00:00:00:00:00";
+    private static final String DST_MAC_ADDR = "00:00:00:00:00:01";
+    private static final ProviderId PID = new ProviderId("of", "foo");
+
+    private static final DeviceId DID1 = DeviceId.deviceId("of:001");
+    private static final DeviceId DID2 = DeviceId.deviceId("of:002");
+    private static final PortNumber PORT_NUM1 = PortNumber.portNumber(1);
+    private static final PortNumber PORT_NUM2 = PortNumber.portNumber(2);
+    private static final PortNumber PORT_NUM3 = PortNumber.portNumber(3);
+    private static final PortNumber PORT_NUM4 = PortNumber.portNumber(4);
+
+    private static final DefaultAnnotations ANNOTATIONS =
+            DefaultAnnotations.builder().set("foo", "bar").build();
+
+    private static final Device DEV1 =
+            new DefaultDevice(PID, DID1, Device.Type.SWITCH, "", "", "", "", null);
+    private static final Device DEV2 =
+            new DefaultDevice(PID, DID2, Device.Type.SWITCH, "", "", "", "", null);
+    private static final Port PORT11 =
+            new DefaultPort(DEV1, PORT_NUM1, true, ANNOTATIONS);
+    private static final Port PORT12 =
+            new DefaultPort(DEV1, PORT_NUM2, true, ANNOTATIONS);
+    private static final Port PORT21 =
+            new DefaultPort(DEV2, PORT_NUM3, true, ANNOTATIONS);
+    private static final Port PORT22 =
+            new DefaultPort(DEV2, PORT_NUM4, true, ANNOTATIONS);
+
+    private static final ConnectPoint CP11 = new ConnectPoint(DID1, PORT_NUM1);
+    private static final ConnectPoint CP12 = new ConnectPoint(DID1, PORT_NUM2);
+    private static final ConnectPoint CP21 = new ConnectPoint(DID2, PORT_NUM3);
+    private static final ConnectPoint CP22 = new ConnectPoint(DID2, PORT_NUM4);
+    private static final Link LINK1 = DefaultLink.builder()
+            .src(CP12).dst(CP21).providerId(PID).type(Link.Type.DIRECT).build();
+
+    private static final TenantId TENANT_ID = TenantId.tenantId("1");
+    private static final NetworkId VNET_ID = NetworkId.networkId(1);
+    private static final DeviceId VDID = DeviceId.deviceId("of:100");
+
+    private static final PortNumber VPORT_NUM1 = PortNumber.portNumber(10);
+    private static final PortNumber VPORT_NUM2 = PortNumber.portNumber(11);
+
+    private static final VirtualNetwork VNET = new DefaultVirtualNetwork(
+            VNET_ID, TenantId.tenantId("t1"));
+    private static final VirtualDevice VDEV =
+            new DefaultVirtualDevice(VNET_ID, VDID);
+    private static final VirtualPort VPORT1 =
+            new DefaultVirtualPort(VNET_ID, VDEV, VPORT_NUM1, CP11);
+    private static final VirtualPort VPORT2 =
+            new DefaultVirtualPort(VNET_ID, VDEV, VPORT_NUM2, CP22);
+    private static final ConnectPoint VCP11 = new ConnectPoint(VDID, VPORT_NUM1);
+    private static final ConnectPoint VCP12 = new ConnectPoint(VDID, VPORT_NUM2);
+
+    protected DefaultVirtualPacketProvider virtualProvider;
+    protected TestPacketService testPacketService;
+    protected TestVirtualPacketProviderService providerService;
+
+    private VirtualProviderManager providerManager;
+
+    private ApplicationId vAppId;
+
+    @Before
+    public void setUp() {
+        virtualProvider = new DefaultVirtualPacketProvider();
+
+        virtualProvider.coreService = new CoreServiceAdapter();
+        virtualProvider.vnaService =
+                new TestVirtualNetworkAdminService();
+
+        providerService = new TestVirtualPacketProviderService();
+
+        testPacketService = new TestPacketService();
+        virtualProvider.packetService = testPacketService;
+
+        providerManager = new VirtualProviderManager();
+        virtualProvider.providerRegistryService = providerManager;
+        providerManager.registerProviderService(VNET_ID, providerService);
+
+        virtualProvider.activate();
+        vAppId = new TestApplicationId(0, "Virtual App");
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+
+        virtualProvider.startPacketHandling();
+    }
+
+    @After
+    public void tearDown() {
+        virtualProvider.deactivate();
+        virtualProvider.coreService = null;
+        virtualProvider.vnaService = null;
+    }
+
+
+    /** Test the virtual outbound packet is delivered to a proper (physical)
+     *  device.
+     */
+    @Test
+    public void devirtualizePacket() {
+        TrafficTreatment tr = DefaultTrafficTreatment.builder()
+                .setOutput(VPORT_NUM1).build();
+        ByteBuffer data = ByteBuffer.wrap("abc".getBytes());
+
+        OutboundPacket vOutPacket = new DefaultOutboundPacket(VDID, tr, data);
+
+        virtualProvider.emit(VNET_ID, vOutPacket);
+
+        assertEquals("The count should be 1", 1,
+                     testPacketService.getRequestedPacketCount());
+
+        OutboundPacket pOutPacket = testPacketService.getRequestedPacket(0);
+
+        assertEquals("The packet should be requested on DEV1", DID1,
+                     pOutPacket.sendThrough());
+
+        PortNumber outPort = pOutPacket.treatment()
+                .allInstructions()
+                .stream()
+                .filter(i -> i.type() == Instruction.Type.OUTPUT)
+                .map(i -> (Instructions.OutputInstruction) i)
+                .map(i -> i.port())
+                .findFirst().get();
+        assertEquals("The packet should be out at PORT1 of DEV1", PORT_NUM1,
+                     outPort);
+    }
+
+    /** Test the physical packet context is delivered to a proper (physical)
+     *  virtual network and device.
+     */
+    @Test
+    public void virtualizePacket() {
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(SRC_MAC_ADDR);
+        eth.setDestinationMACAddress(DST_MAC_ADDR);
+        eth.setVlanID((short) 1);
+        eth.setPayload(null);
+
+        InboundPacket pInPacket =
+                new DefaultInboundPacket(CP22, eth,
+                                         ByteBuffer.wrap(eth.serialize()));
+
+        PacketContext pContext =
+                new TestPacketContext(System.nanoTime(), pInPacket, null, false);
+
+        testPacketService.sendTestPacketContext(pContext);
+
+        PacketContext vContext = providerService.getRequestedPacketContext(0);
+        InboundPacket vInPacket = vContext.inPacket();
+
+        assertEquals("the packet should be received from VCP12",
+                     VCP12, vInPacket.receivedFrom());
+
+        assertEquals("VLAN tag should be excludede", VlanId.UNTAGGED,
+                     vInPacket.parsed().getVlanID());
+    }
+
+    private class TestPacketContext extends DefaultPacketContext {
+
+        /**
+         * Creates a new packet context.
+         *
+         * @param time   creation time
+         * @param inPkt  inbound packet
+         * @param outPkt outbound packet
+         * @param block  whether the context is blocked or not
+         */
+        protected TestPacketContext(long time, InboundPacket inPkt,
+                                    OutboundPacket outPkt, boolean block) {
+            super(time, inPkt, outPkt, block);
+        }
+
+        @Override
+        public void send() {
+
+        }
+    }
+
+    private static class TestApplicationId extends DefaultApplicationId {
+        public TestApplicationId(int id, String name) {
+            super(id, name);
+        }
+    }
+
+    private static class TestVirtualNetworkAdminService
+            extends VirtualNetworkAdminServiceAdapter {
+
+        @Override
+        public Set<VirtualNetwork> getVirtualNetworks(TenantId tenantId) {
+            return ImmutableSet.of(VNET);
+        }
+
+        @Override
+        public Set<VirtualDevice> getVirtualDevices(NetworkId networkId) {
+            return ImmutableSet.of(VDEV);
+        }
+
+        @Override
+        public Set<VirtualPort> getVirtualPorts(NetworkId networkId,
+                                                DeviceId deviceId) {
+            return ImmutableSet.of(VPORT1, VPORT2);
+        }
+
+        @Override
+        public Set<TenantId> getTenantIds() {
+            return ImmutableSet.of(TENANT_ID);
+        }
+
+    }
+
+    private static class TestVirtualPacketProviderService
+            extends AbstractVirtualProviderService<VirtualPacketProvider>
+            implements VirtualPacketProviderService {
+
+        static List<PacketContext> requestedContext = new LinkedList();
+        static List<NetworkId> requestedNetworkId = new LinkedList();
+
+        @Override
+        public VirtualPacketProvider provider() {
+            return null;
+        }
+
+        PacketContext getRequestedPacketContext(int index) {
+            return requestedContext.get(index);
+        }
+
+        @Override
+        public void processPacket(PacketContext context) {
+            requestedContext.add(context);
+        }
+    }
+
+    private static class TestPacketService extends PacketServiceAdapter {
+        static List<OutboundPacket> requestedPacket = new LinkedList();
+        static PacketProcessor processor = null;
+
+        @Override
+        public void addProcessor(PacketProcessor processor, int priority) {
+            this.processor = processor;
+        }
+
+        @Override
+        public void emit(OutboundPacket packet) {
+            requestedPacket.add(packet);
+        }
+
+        OutboundPacket getRequestedPacket(int index) {
+            return requestedPacket.get(index);
+        }
+
+        int getRequestedPacketCount() {
+            return requestedPacket.size();
+        }
+
+        void sendTestPacketContext(PacketContext context) {
+            processor.process(context);
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualNetworkTopologyProviderTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualNetworkTopologyProviderTest.java
new file mode 100644
index 0000000..ee03783
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualNetworkTopologyProviderTest.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.IdGenerator;
+import org.onosproject.event.Event;
+import org.onosproject.incubator.net.tunnel.TunnelId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.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.provider.VirtualNetworkProvider;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderRegistry;
+import org.onosproject.incubator.net.virtual.provider.VirtualNetworkProviderService;
+import org.onosproject.incubator.net.virtual.impl.VirtualNetworkManager;
+import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.TestDeviceParams;
+import org.onosproject.net.intent.FakeIntentManager;
+import org.onosproject.net.intent.TestableIntentService;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.provider.AbstractProviderService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.topology.TopologyEvent;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.*;
+
+/**
+ * Junit tests for VirtualNetworkTopologyProvider.
+ */
+public class VirtualNetworkTopologyProviderTest extends TestDeviceParams {
+
+    private final String tenantIdValue1 = "TENANT_ID1";
+
+    private VirtualNetwork virtualNetwork;
+    private VirtualDevice virtualDevice1;
+    private VirtualDevice virtualDevice2;
+    private VirtualDevice virtualDevice3;
+    private VirtualDevice virtualDevice4;
+    private VirtualDevice virtualDevice5;
+    private ConnectPoint cp1;
+    private ConnectPoint cp2;
+    private ConnectPoint cp3;
+    private ConnectPoint cp4;
+    private ConnectPoint cp5;
+    private ConnectPoint cp6;
+    private ConnectPoint cp7;
+    private ConnectPoint cp8;
+    private ConnectPoint cp9;
+
+    private VirtualNetworkManager manager;
+    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
+    private CoreService coreService;
+    private DefaultVirtualNetworkProvider topologyProvider;
+    private TopologyService topologyService;
+    private TestableIntentService intentService = new FakeIntentManager();
+    private TestServiceDirectory testDirectory;
+    private final VirtualNetworkRegistryAdapter virtualNetworkRegistry = new VirtualNetworkRegistryAdapter();
+
+    private static final int MAX_WAIT_TIME = 5;
+    private static final int MAX_PERMITS = 1;
+    private static Semaphore changed;
+
+    private Set<Set<ConnectPoint>> clusters;
+
+    @Before
+    public void setUp() throws Exception {
+
+        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();
+
+        coreService = new VirtualNetworkTopologyProviderTest.TestCoreService();
+
+        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
+        TestUtils.setField(virtualNetworkManagerStore, "storageService",
+                           new TestStorageService());
+        virtualNetworkManagerStore.activate();
+
+        manager = new VirtualNetworkManager();
+        TestUtils.setField(manager, "coreService", coreService);
+        TestUtils.setField(manager, "store", virtualNetworkManagerStore);
+        TestUtils.setField(manager, "intentService", intentService);
+        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());
+
+        testDirectory = new TestServiceDirectory();
+        TestUtils.setField(manager, "serviceDirectory", testDirectory);
+
+        manager.activate();
+
+        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
+        virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));
+
+        topologyService = manager.get(virtualNetwork.id(), TopologyService.class);
+        topologyProvider = new DefaultVirtualNetworkProvider();
+        topologyProvider.topologyService = topologyService;
+        topologyProvider.providerRegistry = virtualNetworkRegistry;
+        topologyProvider.activate();
+
+        setupVirtualNetworkTopology();
+        changed = new Semaphore(0, true);
+    }
+
+    @After
+    public void tearDown() {
+        topologyProvider.deactivate();
+        virtualNetworkManagerStore.deactivate();
+        manager.deactivate();
+        NetTestTools.injectEventDispatcher(manager, null);
+    }
+
+    /**
+     * Method to create the virtual network for further testing.
+     **/
+    private void setupVirtualNetworkTopology() {
+        virtualDevice1 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID1);
+        virtualDevice2 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID2);
+        virtualDevice3 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID3);
+        virtualDevice4 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID4);
+        virtualDevice5 =
+                manager.createVirtualDevice(virtualNetwork.id(), DID5);
+
+        cp1 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(1));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice1.id(),
+                                  PortNumber.portNumber(1), cp1);
+
+        cp2 = new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(2));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice1.id(),
+                                  PortNumber.portNumber(2), cp2);
+
+        cp3 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(3));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice2.id(),
+                                  PortNumber.portNumber(3), cp3);
+
+        cp4 = new ConnectPoint(virtualDevice2.id(), PortNumber.portNumber(4));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice2.id(),
+                                  PortNumber.portNumber(4), cp4);
+
+        cp5 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(5));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice3.id(),
+                                  PortNumber.portNumber(5), cp5);
+
+        cp6 = new ConnectPoint(virtualDevice3.id(), PortNumber.portNumber(6));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice3.id(),
+                                  PortNumber.portNumber(6), cp6);
+
+        cp7 = new ConnectPoint(virtualDevice4.id(), PortNumber.portNumber(7));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice4.id(),
+                                  PortNumber.portNumber(7), cp7);
+
+        cp8 = new ConnectPoint(virtualDevice4.id(), PortNumber.portNumber(8));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice4.id(),
+                                  PortNumber.portNumber(8), cp8);
+
+        cp9 = new ConnectPoint(virtualDevice5.id(), PortNumber.portNumber(9));
+        manager.createVirtualPort(virtualNetwork.id(), virtualDevice5.id(),
+                                  PortNumber.portNumber(9), cp9);
+
+        VirtualLink link1 = manager.createVirtualLink(virtualNetwork.id(), cp1, cp3);
+        virtualNetworkManagerStore.updateLink(link1, link1.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), cp3, cp1);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link3 = manager.createVirtualLink(virtualNetwork.id(), cp4, cp5);
+        virtualNetworkManagerStore.updateLink(link3, link3.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link4 = manager.createVirtualLink(virtualNetwork.id(), cp5, cp4);
+        virtualNetworkManagerStore.updateLink(link4, link4.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link5 = manager.createVirtualLink(virtualNetwork.id(), cp8, cp9);
+        virtualNetworkManagerStore.updateLink(link5, link5.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link6 = manager.createVirtualLink(virtualNetwork.id(), cp9, cp8);
+        virtualNetworkManagerStore.updateLink(link6, link6.tunnelId(), Link.State.ACTIVE);
+
+        clusters = null;
+    }
+
+    /**
+     * Test isTraversable() method using a null source connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsTraversableNullSrc() {
+        // test the isTraversable() method with a null source connect point.
+        topologyProvider.isTraversable(null, cp3);
+    }
+
+    /**
+     * Test isTraversable() method using a null destination connect point.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testIsTraversableNullDst() {
+        // test the isTraversable() method with a null destination connect point.
+        topologyProvider.isTraversable(cp1, null);
+    }
+
+    /**
+     * Test isTraversable() method.
+     */
+    @Test
+    public void testIsTraversable() {
+        // test the isTraversable() method.
+        assertTrue("These two connect points should be traversable.",
+                   topologyProvider.isTraversable(new ConnectPoint(cp1.elementId(), cp1.port()),
+                                                  new ConnectPoint(cp3.elementId(), cp3.port())));
+        assertTrue("These two connect points should be traversable.",
+                   topologyProvider.isTraversable(new ConnectPoint(cp1.elementId(), cp1.port()),
+                                                  new ConnectPoint(cp5.elementId(), cp5.port())));
+        assertFalse("These two connect points should not be traversable.",
+                    topologyProvider.isTraversable(
+                            new ConnectPoint(virtualDevice1.id(), PortNumber.portNumber(1)),
+                            new ConnectPoint(virtualDevice4.id(), PortNumber.portNumber(6))));
+    }
+
+    /**
+     * Test the topologyChanged() method.
+     */
+    @Test
+    public void testTopologyChanged() {
+        // Initial setup is two clusters of devices/links.
+        assertEquals("The cluster count did not match.", 2,
+                     topologyService.currentTopology().clusterCount());
+
+        // Adding this link will join the two clusters together.
+        List<Event> reasons = new ArrayList<>();
+        VirtualLink link = manager.createVirtualLink(virtualNetwork.id(), cp6, cp7);
+        virtualNetworkManagerStore.updateLink(link, link.tunnelId(), Link.State.ACTIVE);
+        VirtualLink link2 = manager.createVirtualLink(virtualNetwork.id(), cp7, cp6);
+        virtualNetworkManagerStore.updateLink(link2, link2.tunnelId(), Link.State.ACTIVE);
+
+        reasons.add(new LinkEvent(LinkEvent.Type.LINK_ADDED, link));
+        reasons.add(new LinkEvent(LinkEvent.Type.LINK_ADDED, link2));
+        TopologyEvent event = new TopologyEvent(
+                TopologyEvent.Type.TOPOLOGY_CHANGED,
+                topologyService.currentTopology(),
+                reasons);
+
+        topologyProvider.topologyListener.event(event);
+
+        // Wait for the topology changed event, and that the topologyChanged method was called.
+        try {
+            if (!changed.tryAcquire(MAX_PERMITS, MAX_WAIT_TIME, TimeUnit.SECONDS)) {
+                fail("Failed to wait for topology changed event.");
+            }
+        } catch (InterruptedException e) {
+            fail("Semaphore exception." + e.getMessage());
+        }
+
+        // Validate that the topology changed method received a single cluster of connect points.
+        // This means that the two previous clusters have now joined into a single cluster.
+        assertEquals("The cluster count did not match.", 1, this.clusters.size());
+        assertEquals("The cluster count did not match.", 1,
+                     topologyService.currentTopology().clusterCount());
+
+        // Now remove the virtual link to split it back into two clusters.
+        manager.removeVirtualLink(virtualNetwork.id(), link.src(), link.dst());
+        manager.removeVirtualLink(virtualNetwork.id(), link2.src(), link2.dst());
+        assertEquals("The cluster count did not match.", 2,
+                     topologyService.currentTopology().clusterCount());
+
+        reasons = new ArrayList<>();
+        reasons.add(new LinkEvent(LinkEvent.Type.LINK_REMOVED, link));
+        reasons.add(new LinkEvent(LinkEvent.Type.LINK_REMOVED, link2));
+        event = new TopologyEvent(
+                TopologyEvent.Type.TOPOLOGY_CHANGED,
+                topologyService.currentTopology(),
+                reasons);
+
+        topologyProvider.topologyListener.event(event);
+
+        // Wait for the topology changed event, and that the topologyChanged method was called.
+        try {
+            if (!changed.tryAcquire(MAX_PERMITS, MAX_WAIT_TIME, TimeUnit.SECONDS)) {
+                fail("Failed to wait for topology changed event.");
+            }
+        } catch (InterruptedException e) {
+            fail("Semaphore exception." + e.getMessage());
+        }
+
+        // Validate that the topology changed method received two clusters of connect points.
+        // This means that the single previous clusters has now split into two clusters.
+        assertEquals("The cluster count did not match.", 2, this.clusters.size());
+    }
+
+    /**
+     * Virtual network registry implementation for this test class.
+     */
+    private class VirtualNetworkRegistryAdapter implements VirtualNetworkProviderRegistry {
+        private VirtualNetworkProvider provider;
+
+        @Override
+        public VirtualNetworkProviderService register(VirtualNetworkProvider theProvider) {
+            this.provider = theProvider;
+            return new TestVirtualNetworkProviderService(theProvider);
+        }
+
+        @Override
+        public void unregister(VirtualNetworkProvider theProvider) {
+            this.provider = null;
+        }
+
+        @Override
+        public Set<ProviderId> getProviders() {
+            return null;
+        }
+    }
+
+
+    /**
+     * Virtual network provider service implementation for this test class.
+     */
+    private class TestVirtualNetworkProviderService
+            extends AbstractProviderService<VirtualNetworkProvider>
+            implements VirtualNetworkProviderService {
+
+        /**
+         * Constructor.
+         *
+         * @param provider virtual network test provider
+         */
+        protected TestVirtualNetworkProviderService(VirtualNetworkProvider provider) {
+            super(provider);
+        }
+
+        @Override
+        public void topologyChanged(Set<Set<ConnectPoint>> theClusters) {
+            clusters = theClusters;
+            changed.release();
+        }
+
+        @Override
+        public void tunnelUp(NetworkId networkId, ConnectPoint src,
+                             ConnectPoint dst, TunnelId tunnelId) {
+        }
+
+        @Override
+        public void tunnelDown(NetworkId networkId, ConnectPoint src,
+                               ConnectPoint dst, TunnelId tunnelId) {
+        }
+    }
+
+    /**
+     * Core service test class.
+     */
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public IdGenerator getIdGenerator(String topic) {
+            return new IdGenerator() {
+                private AtomicLong counter = new AtomicLong(0);
+
+                @Override
+                public long getNewId() {
+                    return counter.getAndIncrement();
+                }
+            };
+        }
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManagerTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManagerTest.java
new file mode 100644
index 0000000..90e1664
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/impl/provider/VirtualProviderManagerTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.impl.provider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProvider;
+import org.onosproject.incubator.net.virtual.provider.AbstractVirtualProviderService;
+import org.onosproject.net.provider.ProviderId;
+
+import static org.junit.Assert.assertEquals;
+
+public class VirtualProviderManagerTest {
+
+    private static final String TEST_SCHEME1 = "test1";
+    private static final String TEST_SCHEME2 = "test2";
+    private static final String TEST_ID1 = "org.onosproject.virtual.testprovider1";
+    private static final String TEST_ID2 = "org.onosproject.virtual.testprovider1";
+    private static final NetworkId NETWORK_ID1 = NetworkId.networkId(1);
+    private static final NetworkId NETWORK_ID2 = NetworkId.networkId(2);
+
+    VirtualProviderManager virtualProviderManager;
+
+    @Before
+    public void setUp() throws Exception {
+        virtualProviderManager = new VirtualProviderManager();
+    }
+
+    /**
+     * Tests registerProvider() and unregisterProvider().
+     */
+    @Test
+    public void registerProviderTest() {
+        TestProvider1 provider1 = new TestProvider1();
+        virtualProviderManager.registerProvider(provider1);
+
+        assertEquals("The number of registered provider did not match.", 1,
+                     virtualProviderManager.getProviders().size());
+
+        assertEquals("The registered provider did not match", provider1,
+                     virtualProviderManager.getProvider(TEST_SCHEME1));
+
+        virtualProviderManager.unregisterProvider(provider1);
+
+        TestProvider2 provider2 = new TestProvider2();
+        virtualProviderManager.registerProvider(provider2);
+
+        assertEquals("The number of registered provider did not match.", 1,
+                     virtualProviderManager.getProviders().size());
+
+        virtualProviderManager.unregisterProvider(provider2);
+
+        assertEquals("The number of registered provider did not match.", 0,
+                     virtualProviderManager.getProviders().size());
+    }
+
+    /**
+     * Tests registerProviderService() and getProviderService().
+     */
+    @Test
+    public void registerProviderServiceTest() {
+        TestProvider1 provider1 = new TestProvider1();
+        virtualProviderManager.registerProvider(provider1);
+
+        TestProviderService1 providerService1 = new TestProviderService1();
+        virtualProviderManager.registerProviderService(NETWORK_ID1, providerService1);
+
+        assertEquals(providerService1,
+                     virtualProviderManager.getProviderService(NETWORK_ID1, TestProvider1.class));
+    }
+
+    private class TestProvider1 extends AbstractVirtualProvider {
+        protected TestProvider1() {
+            super(new ProviderId(TEST_SCHEME1, TEST_ID1));
+        }
+    }
+
+    private class TestProvider2 extends AbstractVirtualProvider {
+        protected TestProvider2() {
+            super(new ProviderId(TEST_SCHEME2, TEST_ID2));
+        }
+    }
+
+    private class TestProviderService1 extends AbstractVirtualProviderService<TestProvider1> {
+    }
+
+    private class TestProviderService2 extends AbstractVirtualProviderService<TestProvider2> {
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/rest/TenantWebResourceTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/rest/TenantWebResourceTest.java
new file mode 100644
index 0000000..def32c5
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/rest/TenantWebResourceTest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.rest;
+
+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.Ignore;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualNetworkAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+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.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit tests for tenant REST APIs.
+ */
+@Ignore
+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);
+
+        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_NO_CONTENT));
+
+        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/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResourceTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResourceTest.java
new file mode 100644
index 0000000..3b1bd51
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/rest/VirtualNetworkWebResourceTest.java
@@ -0,0 +1,1270 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.rest;
+
+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 com.google.common.collect.Sets;
+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.Ignore;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.codec.CodecService;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.incubator.net.virtual.DefaultVirtualDevice;
+import org.onosproject.incubator.net.virtual.DefaultVirtualHost;
+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.net.TenantId;
+import org.onosproject.incubator.net.virtual.VirtualDevice;
+import org.onosproject.incubator.net.virtual.VirtualHost;
+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.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.PortNumber;
+import org.onosproject.rest.resources.HostResourceTest;
+import org.onosproject.rest.resources.LinksResourceTest;
+import org.onosproject.rest.resources.ResourceTest;
+
+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.Set;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Unit tests for virtual network REST APIs.
+ */
+@Ignore
+public class VirtualNetworkWebResourceTest extends ResourceTest {
+
+    private final VirtualNetworkAdminService mockVnetAdminService = createMock(VirtualNetworkAdminService.class);
+    private final VirtualNetworkService mockVnetService = createMock(VirtualNetworkService.class);
+    private CodecManager codecService;
+
+    private final HashSet<VirtualDevice> vdevSet = new HashSet<>();
+    private 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 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");
+
+    private final ConnectPoint cp1 = new ConnectPoint(dev1.id(), portNumber(1));
+    private final ConnectPoint cp2 = new ConnectPoint(dev2.id(), portNumber(2));
+
+    private final VirtualPort vport22 = new DefaultVirtualPort(networkId3,
+                                                               dev22, portNumber(22), cp1);
+    private final VirtualPort vport23 = new DefaultVirtualPort(networkId3,
+                                                               dev22, portNumber(23), cp2);
+
+    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 VirtualLink vlink1 = DefaultVirtualLink.builder()
+            .networkId(networkId3)
+            .src(cp22)
+            .dst(cp11)
+            .build();
+
+    private final VirtualLink vlink2 = DefaultVirtualLink.builder()
+            .networkId(networkId3)
+            .src(cp12)
+            .dst(cp21)
+            .build();
+
+    private final MacAddress mac1 = MacAddress.valueOf("00:11:00:00:00:01");
+    private final MacAddress mac2 = MacAddress.valueOf("00:22:00:00:00:02");
+    private final VlanId vlan1 = VlanId.vlanId((short) 11);
+    private final VlanId vlan2 = VlanId.vlanId((short) 22);
+    private final IpAddress ip1 = IpAddress.valueOf("10.0.0.1");
+    private final IpAddress ip2 = IpAddress.valueOf("10.0.0.2");
+    private final IpAddress ip3 = IpAddress.valueOf("10.0.0.3");
+
+    private final HostId hId1 = HostId.hostId(mac1, vlan1);
+    private final HostId hId2 = HostId.hostId(mac2, vlan2);
+    private final HostLocation loc1 = new HostLocation(devId1, portNumber(100), 123L);
+    private final HostLocation loc2 = new HostLocation(devId2, portNumber(200), 123L);
+    private final Set<IpAddress> ipSet1 = Sets.newHashSet(ip1, ip2);
+    private final Set<IpAddress> ipSet2 = Sets.newHashSet(ip1, ip3);
+    private final VirtualHost vhost1 = new DefaultVirtualHost(networkId1, hId1,
+                                                              mac1, vlan1, loc1, ipSet1);
+    private final VirtualHost vhost2 = new DefaultVirtualHost(networkId2, hId2,
+                                                              mac2, vlan2, loc2, ipSet2);
+
+
+
+
+    /**
+     * 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);
+
+        setServiceDirectory(testDirectory);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual network entity representation in JSON matches
+     * the actual virtual network entity.
+     */
+    private static final 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
+
+        private 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>            the type of virtual network object
+     * @return matcher
+     */
+    private static <T> JsonObjectMatcher matchesVnetEntity(T obj, List<String> jsonFieldNames,
+                                                           BiFunction<T, String, String> getValue) {
+        return new JsonObjectMatcher<T>(obj, jsonFieldNames, getValue);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual network entity is represented properly in a JSON
+     * array of virtual network entities.
+     */
+    protected 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
+
+        protected 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.
+     */
+    private static final class VnetJsonArrayMatcher extends JsonArrayMatcher<VirtualNetwork> {
+
+        private VnetJsonArrayMatcher(VirtualNetwork vnetIn) {
+            super(vnetIn,
+                  vnet -> "Virtual network " + vnet.id().toString(),
+                  (vnet, jsonObject) -> jsonObject.get(ID).asString().equals(vnet.id().toString()),
+                  ImmutableList.of(ID, TENANT_ID),
+                  (vnet, s) -> 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() {
+        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").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 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
+    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 {
+            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_NO_CONTENT));
+
+        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.
+     */
+    private static final class VdevJsonArrayMatcher extends JsonArrayMatcher<VirtualDevice> {
+
+        private VdevJsonArrayMatcher(VirtualDevice vdevIn) {
+            super(vdevIn,
+                  vdev -> "Virtual device " + vdev.networkId().toString()
+                          + " " + vdev.id().toString(),
+                  (vdev, jsonObject) -> jsonObject.get(ID).asString().equals(vdev.networkId().toString())
+                          && jsonObject.get(DEVICE_ID).asString().equals(vdev.id().toString()),
+                  ImmutableList.of(ID, DEVICE_ID),
+                  (vdev, s) -> 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";
+            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_NO_CONTENT));
+
+        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.
+     */
+    private static final class VportJsonArrayMatcher extends JsonArrayMatcher<VirtualPort> {
+
+        private VportJsonArrayMatcher(VirtualPort vportIn) {
+            super(vportIn,
+                  vport -> "Virtual port " + vport.networkId().toString() + " "
+                    + vport.element().id().toString() + " " + vport.number().toString(),
+                  (vport, jsonObject) -> 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) -> 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().deviceId().toString()
+                          : s.equals(PHYS_PORT_NUM) ? vport.realizedBy().port().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);
+        ConnectPoint cp1 = new ConnectPoint(physDevice.id(), portNumber(1));
+        expect(mockVnetAdminService.createVirtualPort(networkId, deviceId, portNumber(22), cp1))
+                .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";
+            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_NO_CONTENT));
+
+        verify(mockVnetAdminService);
+    }
+
+    // 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.
+     */
+    private static final class VirtualLinkJsonMatcher extends LinksResourceTest.LinkJsonMatcher {
+        private final VirtualLink vlink;
+        private String reason = "";
+
+        private 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;
+            }
+            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 final class VirtualLinkJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+        private final VirtualLink vlink;
+        private String reason = "";
+
+        private VirtualLinkJsonArrayMatcher(VirtualLink vlinkValue) {
+            vlink = vlinkValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonArray json) {
+            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))
+                .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().method("DELETE", Entity.json(jsonStream));
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT));
+        verify(mockVnetAdminService);
+    }
+
+    // Tests for Virtual Hosts
+
+    /**
+     * Tests the result of the REST API GET when there are no virtual hosts.
+     */
+    @Test
+    public void testGetVirtualHostsEmptyArray() {
+        NetworkId networkId = networkId4;
+        expect(mockVnetService.getVirtualHosts(networkId)).andReturn(ImmutableSet.of()).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString() + "/hosts";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, is("{\"hosts\":[]}"));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Tests the result of the REST API GET when virtual hosts are defined.
+     */
+    @Test
+    public void testGetVirtualHostsArray() {
+        NetworkId networkId = networkId3;
+        final Set<VirtualHost> vhostSet = ImmutableSet.of(vhost1, vhost2);
+        expect(mockVnetService.getVirtualHosts(networkId)).andReturn(vhostSet).anyTimes();
+        replay(mockVnetService);
+
+        WebTarget wt = target();
+        String location = "vnets/" + networkId.toString() + "/hosts";
+        String response = wt.path(location).request().get(String.class);
+        assertThat(response, containsString("{\"hosts\":["));
+
+        final JsonObject result = Json.parse(response).asObject();
+        assertThat(result, notNullValue());
+
+        assertThat(result.names(), hasSize(1));
+        assertThat(result.names().get(0), is("hosts"));
+
+        final JsonArray vnetJsonArray = result.get("hosts").asArray();
+        assertThat(vnetJsonArray, notNullValue());
+        assertEquals("Virtual hosts array is not the correct size.",
+                     vhostSet.size(), vnetJsonArray.size());
+
+        vhostSet.forEach(vhost -> assertThat(vnetJsonArray, hasVhost(vhost)));
+
+        verify(mockVnetService);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual host representation in JSON matches
+     * the actual virtual host.
+     */
+    private static final class VirtualHostJsonMatcher extends HostResourceTest.HostJsonMatcher {
+        private final VirtualHost vhost;
+        private String reason = "";
+
+        private VirtualHostJsonMatcher(VirtualHost vhostValue) {
+            super(vhostValue);
+            vhost = vhostValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonObject jsonHost) {
+            if (!super.matchesSafely(jsonHost)) {
+                return false;
+            }
+            // check NetworkId
+            String jsonNetworkId = jsonHost.get(ID).asString();
+            String networkId = vhost.networkId().toString();
+            if (!jsonNetworkId.equals(networkId)) {
+                reason = ID + " was " + jsonNetworkId;
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual host matcher.
+     *
+     * @param vhost virtual host object we are looking for
+     * @return matcher
+     */
+    private static VirtualHostJsonMatcher matchesVirtualHost(VirtualHost vhost) {
+        return new VirtualHostJsonMatcher(vhost);
+    }
+
+    /**
+     * Hamcrest matcher to check that a virtual host is represented properly in a JSON
+     * array of hosts.
+     */
+    private static final class VirtualHostJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+        private final VirtualHost vhost;
+        private String reason = "";
+
+        private VirtualHostJsonArrayMatcher(VirtualHost vhostValue) {
+            vhost = vhostValue;
+        }
+
+        @Override
+        public boolean matchesSafely(JsonArray json) {
+            for (int jsonHostIndex = 0; jsonHostIndex < json.size();
+                 jsonHostIndex++) {
+
+                JsonObject jsonHost = json.get(jsonHostIndex).asObject();
+
+                if (matchesVirtualHost(vhost).matchesSafely(jsonHost)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(reason);
+        }
+    }
+
+    /**
+     * Factory to allocate a virtual host array matcher.
+     *
+     * @param vhost virtual host object we are looking for
+     * @return matcher
+     */
+    private VirtualHostJsonArrayMatcher hasVhost(VirtualHost vhost) {
+        return new VirtualHostJsonArrayMatcher(vhost);
+    }
+
+    /**
+     * Tests adding of new virtual host using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualHost() {
+        NetworkId networkId = networkId3;
+        expect(mockVnetAdminService.createVirtualHost(networkId, hId1, mac1, vlan1, loc1, ipSet1))
+                .andReturn(vhost1);
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-host.json");
+        String reqLocation = "vnets/" + networkId.toString() + "/hosts";
+        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 host using POST via JSON stream.
+     */
+    @Test
+    public void testPostVirtualHostNullJsonStream() {
+        NetworkId networkId = networkId3;
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target();
+        try {
+            String reqLocation = "vnets/" + networkId.toString() + "/hosts";
+            wt.path(reqLocation)
+                    .request(MediaType.APPLICATION_JSON_TYPE)
+                    .post(Entity.json(null), String.class);
+            fail("POST of null virtual host did not throw an exception");
+        } catch (BadRequestException ex) {
+            assertThat(ex.getMessage(), containsString("HTTP 400 Bad Request"));
+        }
+
+        verify(mockVnetAdminService);
+    }
+
+    /**
+     * Tests removing a virtual host with DELETE request.
+     */
+    @Test
+    public void testDeleteVirtualHost() {
+        NetworkId networkId = networkId3;
+        mockVnetAdminService.removeVirtualHost(networkId, hId1);
+        expectLastCall();
+        replay(mockVnetAdminService);
+
+        WebTarget wt = target()
+                .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
+        InputStream jsonStream = VirtualNetworkWebResourceTest.class
+                .getResourceAsStream("post-virtual-host.json");
+        String reqLocation = "vnets/" + networkId.toString() + "/hosts";
+        Response response = wt.path(reqLocation).request().method("DELETE", Entity.json(jsonStream));
+
+        assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT));
+        verify(mockVnetAdminService);
+    }
+}
diff --git a/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStoreTest.java b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStoreTest.java
new file mode 100644
index 0000000..58d3506
--- /dev/null
+++ b/apps/virtual/app/src/test/java/org/onosproject/incubator/net/virtual/store/impl/SimpleVirtualMastershipStoreTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.incubator.net.virtual.store.impl;
+
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipTerm;
+import org.onosproject.net.DeviceId;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.onosproject.net.MastershipRole.MASTER;
+import static org.onosproject.net.MastershipRole.NONE;
+import static org.onosproject.net.MastershipRole.STANDBY;
+
+public class SimpleVirtualMastershipStoreTest {
+
+    private static final NetworkId VNID1 = NetworkId.networkId(1);
+
+    private static final DeviceId VDID1 = DeviceId.deviceId("of:01");
+    private static final DeviceId VDID2 = DeviceId.deviceId("of:02");
+    private static final DeviceId VDID3 = DeviceId.deviceId("of:03");
+    private static final DeviceId VDID4 = DeviceId.deviceId("of:04");
+
+    private static final NodeId N1 = new NodeId("local");
+    private static final NodeId N2 = new NodeId("other");
+
+    private SimpleVirtualMastershipStore sms;
+
+    @Before
+    public void setUp() throws Exception {
+        sms = new SimpleVirtualMastershipStore();
+        sms.activate();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        sms.deactivate();
+    }
+
+    @Test
+    public void getRole() {
+        //special case, no backup or master
+        put(VNID1, VDID1, N1, false, false);
+        assertEquals("wrong role", NONE, sms.getRole(VNID1, N1, VDID1));
+
+        //backup exists but we aren't mapped
+        put(VNID1, VDID2, N1, false, true);
+        assertEquals("wrong role", STANDBY, sms.getRole(VNID1, N1, VDID2));
+
+        //N2 is master
+        put(VNID1, VDID3, N2, true, true);
+        assertEquals("wrong role", MASTER, sms.getRole(VNID1, N2, VDID3));
+
+        //N2 is master but N1 is only in backups set
+        put(VNID1, VDID4, N1, false, true);
+        put(VNID1, VDID4, N2, true, false);
+        assertEquals("wrong role", STANDBY, sms.getRole(VNID1, N1, VDID4));
+    }
+
+    @Test
+    public void getMaster() {
+        put(VNID1, VDID3, N2, true, true);
+        assertEquals("wrong role", MASTER, sms.getRole(VNID1, N2, VDID3));
+        assertEquals("wrong node", N2, sms.getMaster(VNID1, VDID3));
+    }
+
+    @Test
+    public void setMaster() {
+        put(VNID1, VDID1, N1, false, false);
+        assertEquals("wrong event", MASTER_CHANGED,
+                     Futures.getUnchecked(sms.setMaster(VNID1, N1, VDID1)).type());
+        assertEquals("wrong role", MASTER, sms.getRole(VNID1, N1, VDID1));
+        //set node that's already master - should be ignored
+        assertNull("wrong event",
+                   Futures.getUnchecked(sms.setMaster(VNID1, N1, VDID1)));
+
+        //set STANDBY to MASTER
+        put(VNID1, VDID2, N1, false, true);
+        assertEquals("wrong role", STANDBY, sms.getRole(VNID1, N1, VDID2));
+        assertEquals("wrong event", MASTER_CHANGED,
+                     Futures.getUnchecked(sms.setMaster(VNID1, N1, VDID2)).type());
+        assertEquals("wrong role", MASTER, sms.getRole(VNID1, N1, VDID2));
+    }
+
+    @Test
+    public void getDevices() {
+        Set<DeviceId> d = Sets.newHashSet(VDID1, VDID2);
+
+        put(VNID1, VDID1, N2, true, true);
+        put(VNID1, VDID2, N2, true, true);
+        put(VNID1, VDID3, N1, true, true);
+        assertTrue("wrong devices", d.equals(sms.getDevices(VNID1, N2)));
+    }
+
+    @Test
+    public void getTermFor() {
+        put(VNID1, VDID1, N1, true, true);
+        assertEquals("wrong term", MastershipTerm.of(N1, 0),
+                     sms.getTermFor(VNID1, VDID1));
+
+        //switch to N2 and back - 2 term switches
+        sms.setMaster(VNID1, N2, VDID1);
+        sms.setMaster(VNID1, N1, VDID1);
+        assertEquals("wrong term", MastershipTerm.of(N1, 2),
+                     sms.getTermFor(VNID1, VDID1));
+    }
+
+    @Test
+    public void requestRole() {
+        //NONE - become MASTER
+        put(VNID1, VDID1, N1, false, false);
+        assertEquals("wrong role", MASTER,
+                     Futures.getUnchecked(sms.requestRole(VNID1, VDID1)));
+
+        //was STANDBY - become MASTER
+        put(VNID1, VDID2, N1, false, true);
+        assertEquals("wrong role", MASTER,
+                     Futures.getUnchecked(sms.requestRole(VNID1, VDID2)));
+
+        //other MASTER - stay STANDBY
+        put(VNID1, VDID3, N2, true, false);
+        assertEquals("wrong role", STANDBY,
+                     Futures.getUnchecked(sms.requestRole(VNID1, VDID3)));
+
+        //local (N1) is MASTER - stay MASTER
+        put(VNID1, VDID4, N1, true, true);
+        assertEquals("wrong role", MASTER,
+                     Futures.getUnchecked(sms.requestRole(VNID1, VDID4)));
+    }
+
+    @Test
+    public void unsetMaster() {
+        //NONE - record backup but take no other action
+        put(VNID1, VDID1, N1, false, false);
+        sms.setStandby(VNID1, N1, VDID1);
+        assertTrue("not backed up", sms.backupsByNetwork.get(VNID1)
+                .get(VDID1).contains(N1));
+        int prev = sms.termMapByNetwork.get(VNID1).get(VDID1).get();
+        sms.setStandby(VNID1, N1, VDID1);
+        assertEquals("term should not change", prev, sms.termMapByNetwork.get(VNID1)
+                .get(VDID1).get());
+
+        //no backup, MASTER
+        put(VNID1, VDID1, N1, true, false);
+        assertNull("expect no MASTER event",
+                   Futures.getUnchecked(sms.setStandby(VNID1, N1, VDID1)).roleInfo().master());
+        assertNull("wrong node", sms.masterMapByNetwork.get(VNID1).get(VDID1));
+
+        //backup, switch
+        sms.masterMapByNetwork.get(VNID1).clear();
+        put(VNID1, VDID1, N1, true, true);
+        put(VNID1, VDID1, N2, false, true);
+        put(VNID1, VDID2, N2, true, true);
+        MastershipEvent event = Futures.getUnchecked(sms.setStandby(VNID1, N1, VDID1));
+        assertEquals("wrong event", MASTER_CHANGED, event.type());
+        assertEquals("wrong master", N2, event.roleInfo().master());
+    }
+
+    //helper to populate master/backup structures
+    private void put(NetworkId networkId, DeviceId dev, NodeId node,
+                     boolean master, boolean backup) {
+        if (master) {
+            sms.masterMapByNetwork
+                    .computeIfAbsent(networkId, k -> new HashMap<>())
+                    .put(dev, node);
+        } else if (backup) {
+            List<NodeId> stbys = sms.backupsByNetwork
+                    .computeIfAbsent(networkId, k -> new HashMap<>())
+                    .getOrDefault(dev, new ArrayList<>());
+            stbys.add(node);
+            sms.backupsByNetwork.get(networkId).put(dev, stbys);
+        }
+
+        sms.termMapByNetwork
+                .computeIfAbsent(networkId, k -> new HashMap<>())
+                .put(dev, new AtomicInteger());
+    }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/resources/domain-config.json b/apps/virtual/app/src/test/resources/domain-config.json
new file mode 100644
index 0000000..beda11a
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/domain-config.json
@@ -0,0 +1,36 @@
+{
+  "domains" : {
+    "cord" : {
+      "basic" : {
+        "name" : "Core Fabric",
+        "applicationName" : "org.onosproject.testdomain",
+        "internalDevices" : [ "of:1" ],
+        "edgePorts" : [ "of:12/1", "of:14/1" ]
+      }
+    },
+    "mpls" : {
+      "basic" : {
+        "name" : "MPLS Core",
+        "applicationName" : "org.onosproject.testdomain",
+        "internalDevices" : [ "of:2" ],
+        "edgePorts" : [ "of:12/2", "of:23/2" ]
+      }
+    },
+    "dc" : {
+      "basic" : {
+        "name" : "Data Center Fabric",
+        "applicationName" : "org.onosproject.testdomain",
+        "internalDevices" : [ "of:3" ],
+        "edgePorts" : [ "of:23/3", "of:34/3" ]
+      }
+    },
+    "optical" : {
+      "basic" : {
+        "name" : "Optical Core",
+        "applicationName" : "org.onosproject.testdomain",
+        "internalDevices" : [ "of:4" ],
+        "edgePorts" : [ "of:14/4", "of:34/4" ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/resources/fractal-domain-config.json b/apps/virtual/app/src/test/resources/fractal-domain-config.json
new file mode 100644
index 0000000..521c840
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/fractal-domain-config.json
@@ -0,0 +1,28 @@
+{
+  "domains" : {
+    "domain1" : {
+      "basic" : {
+        "name" : "Domain 1",
+        "applicationName" : "org.onosproject.meshdomain",
+        "internalDevices" : [ "of:0000000000001001", "of:0000000000001002", "of:0000000000001003" ],
+        "edgePorts" : [ "of:0000000000010000/1", "of:0000000003010000/2", "of:0000000002010000/1" ]
+      }
+    },
+    "domain2" : {
+      "basic" : {
+        "name" : "Domain 2",
+        "applicationName" : "org.onosproject.meshdomain",
+        "internalDevices" : [ "of:0000000000002001", "of:0000000000002002", "of:0000000000002003" ],
+        "edgePorts" : [ "of:0000000000020000/1", "of:0000000003020000/1", "of:0000000002010000/2" ]
+      }
+    },
+    "domain3" : {
+      "basic" : {
+        "name" : "Domain 3",
+        "applicationName" : "org.onosproject.meshdomain",
+        "internalDevices" : [ "of:0000000000003001", "of:0000000000003002", "of:0000000000003003" ],
+        "edgePorts" : [ "of:0000000000030000/1", "of:0000000003010000/1", "of:0000000003020000/2" ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-tenant.json b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-tenant.json
new file mode 100644
index 0000000..407f9a2
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-tenant.json
@@ -0,0 +1,3 @@
+{
+  "id": "TenantId2"
+}
diff --git a/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-device.json b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-device.json
new file mode 100644
index 0000000..fbd129c
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-device.json
@@ -0,0 +1,4 @@
+{
+  "networkId": "3",
+  "deviceId": "devId2"
+}
diff --git a/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-host.json b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-host.json
new file mode 100644
index 0000000..557dc32
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-host.json
@@ -0,0 +1,16 @@
+{
+  "networkId": "3",
+  "id": "00:11:00:00:00:01/11",
+  "mac": "00:11:00:00:00:01",
+  "vlan": "11",
+  "locations": [
+    {
+      "elementId": "devid1",
+      "port": "100"
+    }
+  ],
+  "ipAddresses": [
+    "10.0.0.1",
+    "10.0.0.2"
+  ]
+}
diff --git a/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-link.json b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-link.json
new file mode 100644
index 0000000..8f04673
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-link.json
@@ -0,0 +1,13 @@
+{
+  "networkId": "3",
+  "src": {
+    "device": "of:devid2",
+    "port": "22" 
+  },
+  "dst": {
+    "device": "of:devid1",
+    "port": "21" 
+  },
+  "type": "VIRTUAL",
+  "state": "ACTIVE"
+}
diff --git a/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-port.json b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-port.json
new file mode 100644
index 0000000..1d60842
--- /dev/null
+++ b/apps/virtual/app/src/test/resources/org/onosproject/incubator/net/virtual/rest/post-virtual-port.json
@@ -0,0 +1,7 @@
+{
+  "networkId": "3",
+  "deviceId": "dev22",
+  "portNum": "22",
+  "physDeviceId": "dev1",
+  "physPortNum": "1"
+}