[ONOS-7731] Add implementation of openstack vtap store and criterion

Change-Id: I7f41652f127038af9d3f79b34d427d28ce162d50
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DefaultOpenstackVtap.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DefaultOpenstackVtap.java
new file mode 100644
index 0000000..7e51be5
--- /dev/null
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DefaultOpenstackVtap.java
@@ -0,0 +1,199 @@
+/*
+ * 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.openstackvtap.impl;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onosproject.net.AbstractDescription;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.openstackvtap.api.OpenstackVtap;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+import org.onosproject.openstackvtap.api.OpenstackVtapId;
+
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Default implementation of an immutable openstack vTap.
+ */
+public final class DefaultOpenstackVtap extends AbstractDescription implements OpenstackVtap {
+
+    private final OpenstackVtapId id;
+    private final Type type;
+    private final OpenstackVtapCriterion vTapCriterion;
+    private final Set<DeviceId> txDeviceIds;
+    private final Set<DeviceId> rxDeviceIds;
+
+    // private constructor not intended to use from external
+    private DefaultOpenstackVtap(OpenstackVtapId id, Type type,
+                                 OpenstackVtapCriterion vTapCriterion,
+                                 Set<DeviceId> txDeviceIds, Set<DeviceId> rxDeviceIds,
+                                 SparseAnnotations... annotations) {
+        super(annotations);
+        this.id = id;
+        this.type = type;
+        this.vTapCriterion = vTapCriterion;
+        this.txDeviceIds = txDeviceIds;
+        this.rxDeviceIds = rxDeviceIds;
+    }
+
+    @Override
+    public OpenstackVtapId id() {
+        return id;
+    }
+
+    @Override
+    public Type type() {
+        return type;
+    }
+
+    @Override
+    public OpenstackVtapCriterion vTapCriterion() {
+        return vTapCriterion;
+    }
+
+    @Override
+    public Set<DeviceId> txDeviceIds() {
+        return ImmutableSet.copyOf(txDeviceIds);
+    }
+
+    @Override
+    public Set<DeviceId> rxDeviceIds() {
+        return ImmutableSet.copyOf(rxDeviceIds);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("id", id)
+                .add("type", type)
+                .add("vTapCriterion", vTapCriterion)
+                .add("txDeviceIds", txDeviceIds)
+                .add("rxDeviceIds", rxDeviceIds)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(),
+                id,
+                type,
+                vTapCriterion,
+                txDeviceIds,
+                rxDeviceIds);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object != null && getClass() == object.getClass()) {
+            if (!super.equals(object)) {
+                return false;
+            }
+            DefaultOpenstackVtap that = (DefaultOpenstackVtap) object;
+            return Objects.equal(this.id, that.id)
+                    && Objects.equal(this.type, that.type)
+                    && Objects.equal(this.vTapCriterion, that.vTapCriterion)
+                    && Objects.equal(this.txDeviceIds, that.txDeviceIds)
+                    && Objects.equal(this.rxDeviceIds, that.rxDeviceIds);
+        }
+        return false;
+    }
+
+    /**
+     * Creates a new default openstack vTap builder.
+     *
+     * @return default openstack vTap builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for DefaultOpenstackVtap object.
+     */
+    public static class Builder implements OpenstackVtap.Builder {
+        private static final SparseAnnotations EMPTY = DefaultAnnotations.builder().build();
+
+        private OpenstackVtapId id;
+        private Type type;
+        private OpenstackVtapCriterion vTapCriterion;
+        private Set<DeviceId> txDeviceIds;
+        private Set<DeviceId> rxDeviceIds;
+        private SparseAnnotations annotations = EMPTY;
+
+        // private constructor not intended to use from external
+        Builder() {
+        }
+
+        @Override
+        public Builder id(OpenstackVtapId id) {
+            this.id = id;
+            return this;
+        }
+
+        @Override
+        public Builder type(Type type) {
+            this.type = type;
+            return this;
+        }
+
+        @Override
+        public Builder vTapCriterion(OpenstackVtapCriterion vTapCriterion) {
+            this.vTapCriterion = vTapCriterion;
+            return this;
+        }
+
+        @Override
+        public Builder txDeviceIds(Set<DeviceId> txDeviceIds) {
+            if (txDeviceIds != null) {
+                this.txDeviceIds = ImmutableSet.copyOf(txDeviceIds);
+            } else {
+                this.txDeviceIds = Sets.newHashSet();
+            }
+            return this;
+        }
+
+        @Override
+        public Builder rxDeviceIds(Set<DeviceId> rxDeviceIds) {
+            if (rxDeviceIds != null) {
+                this.rxDeviceIds = ImmutableSet.copyOf(rxDeviceIds);
+            } else {
+                this.rxDeviceIds = Sets.newHashSet();
+            }
+            return this;
+        }
+
+        @Override
+        public Builder annotations(SparseAnnotations... annotations) {
+            checkArgument(annotations.length <= 1,
+                    "Only one set of annotations is expected");
+            this.annotations = annotations.length == 1 ? annotations[0] : EMPTY;
+            return this;
+        }
+
+        @Override
+        public DefaultOpenstackVtap build() {
+            return new DefaultOpenstackVtap(id, type, vTapCriterion,
+                                            txDeviceIds, rxDeviceIds, annotations);
+        }
+    }
+
+}
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DefaultOpenstackVtapCriterion.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DefaultOpenstackVtapCriterion.java
new file mode 100644
index 0000000..ef8f08f
--- /dev/null
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DefaultOpenstackVtapCriterion.java
@@ -0,0 +1,176 @@
+/*
+ * 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.openstackvtap.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Default implementation of an immutable openstack vTap criterion.
+ */
+public final class DefaultOpenstackVtapCriterion implements OpenstackVtapCriterion {
+    private final IpPrefix srcIpPrefix;
+    private final IpPrefix dstIpPrefix;
+    private final byte     ipProtocol;
+    private final TpPort srcTpPort;
+    private final TpPort   dstTpPort;
+
+    private static final String NOT_NULL_MSG = "Element % cannot be null";
+
+    // private constructor not intended to use from external
+    private DefaultOpenstackVtapCriterion(IpPrefix srcIpPrefix,
+                                          IpPrefix dstIpPrefix,
+                                          byte ipProtocol,
+                                          TpPort srcTpPort,
+                                          TpPort dstTpPort) {
+        this.srcIpPrefix = srcIpPrefix;
+        this.dstIpPrefix = dstIpPrefix;
+        this.ipProtocol  = ipProtocol;
+        this.srcTpPort   = srcTpPort;
+        this.dstTpPort   = dstTpPort;
+    }
+
+    @Override
+    public IpPrefix srcIpPrefix() {
+        return srcIpPrefix;
+    }
+
+    @Override
+    public IpPrefix dstIpPrefix() {
+        return dstIpPrefix;
+    }
+
+    @Override
+    public byte ipProtocol() {
+        return ipProtocol;
+    }
+
+    @Override
+    public TpPort srcTpPort() {
+        return srcTpPort;
+    }
+
+    @Override
+    public TpPort dstTpPort() {
+        return dstTpPort;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("srcIpPrefix", srcIpPrefix)
+                .add("dstIpPrefix", dstIpPrefix)
+                .add("ipProtocol", ipProtocol)
+                .add("srcTpPort", srcTpPort)
+                .add("dstTpPort", dstTpPort)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(),
+                srcIpPrefix,
+                dstIpPrefix,
+                ipProtocol,
+                srcTpPort,
+                dstTpPort);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object != null && getClass() == object.getClass()) {
+            if (!super.equals(object)) {
+                return false;
+            }
+
+            DefaultOpenstackVtapCriterion that = (DefaultOpenstackVtapCriterion) object;
+            return Objects.equal(this.srcIpPrefix, that.srcIpPrefix) &&
+                    Objects.equal(this.dstIpPrefix, that.dstIpPrefix) &&
+                    Objects.equal(this.ipProtocol, that.ipProtocol) &&
+                    Objects.equal(this.srcTpPort, that.srcTpPort) &&
+                    Objects.equal(this.dstTpPort, that.dstTpPort);
+        }
+        return false;
+    }
+
+    /**
+     * Creates a new default openstack vTap criterion builder.
+     *
+     * @return default openstack vTap criterion builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * A builder class for openstack vTap criterion builder.
+     */
+    public static final class Builder implements OpenstackVtapCriterion.Builder {
+        private IpPrefix srcIpPrefix;
+        private IpPrefix dstIpPrefix;
+        private byte     ipProtocol;
+        private TpPort   srcTpPort;
+        private TpPort   dstTpPort;
+
+        // private constructor not intended to use from external
+        Builder() {
+        }
+
+        @Override
+        public DefaultOpenstackVtapCriterion build() {
+            checkArgument(srcIpPrefix != null, NOT_NULL_MSG, "Source IP Prefix");
+            checkArgument(dstIpPrefix != null, NOT_NULL_MSG, "Destination IP Prefix");
+
+            return new DefaultOpenstackVtapCriterion(srcIpPrefix, dstIpPrefix,
+                                                ipProtocol, srcTpPort, dstTpPort);
+        }
+
+        @Override
+        public Builder srcIpPrefix(IpPrefix srcIpPrefix) {
+            this.srcIpPrefix = srcIpPrefix;
+            return this;
+        }
+
+        @Override
+        public Builder dstIpPrefix(IpPrefix dstIpPrefix) {
+            this.dstIpPrefix = dstIpPrefix;
+            return this;
+        }
+
+        @Override
+        public Builder ipProtocol(byte ipProtocol) {
+            this.ipProtocol = ipProtocol;
+            return this;
+        }
+
+        @Override
+        public Builder srcTpPort(TpPort srcTpPort) {
+            this.srcTpPort = srcTpPort;
+            return this;
+        }
+
+        @Override
+        public Builder dstTpPort(TpPort dstTpPort) {
+            this.dstTpPort = dstTpPort;
+            return this;
+        }
+    }
+}
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DistributedOpenstackVtapStore.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DistributedOpenstackVtapStore.java
new file mode 100644
index 0000000..d6f63eb
--- /dev/null
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/DistributedOpenstackVtapStore.java
@@ -0,0 +1,570 @@
+/*
+ * 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.openstackvtap.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.openstackvtap.api.OpenstackVtap;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+import org.onosproject.openstackvtap.api.OpenstackVtapEvent;
+import org.onosproject.openstackvtap.api.OpenstackVtapId;
+import org.onosproject.openstackvtap.api.OpenstackVtapStore;
+import org.onosproject.openstackvtap.api.OpenstackVtapStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedPrimitive.Status;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of users using a {@code ConsistentMap}.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedOpenstackVtapStore
+        extends AbstractStore<OpenstackVtapEvent, OpenstackVtapStoreDelegate>
+        implements OpenstackVtapStore {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    private ConsistentMap<OpenstackVtapId, DefaultOpenstackVtap> vTapConsistentMap;
+    private MapEventListener<OpenstackVtapId, DefaultOpenstackVtap>
+                                            vTapListener = new VTapEventListener();
+    private Map<OpenstackVtapId, DefaultOpenstackVtap> vTapMap;
+
+    private static final Serializer SERIALIZER = Serializer
+            .using(new KryoNamespace.Builder().register(KryoNamespaces.API)
+                    .register(OpenstackVtapId.class)
+                    .register(UUID.class)
+                    .register(DefaultOpenstackVtap.class)
+                    .register(OpenstackVtap.Type.class)
+                    .register(DefaultOpenstackVtapCriterion.class)
+                    .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
+                    .build());
+
+    private Map<DeviceId, Set<OpenstackVtapId>>
+                                    vTapIdsByTxDeviceId = Maps.newConcurrentMap();
+    private Map<DeviceId, Set<OpenstackVtapId>>
+                                    vTapIdsByRxDeviceId = Maps.newConcurrentMap();
+
+    private ScheduledExecutorService eventExecutor;
+
+    private Consumer<Status> vTapStatusListener;
+
+    public static final String INVALID_DESCRIPTION = "Invalid create/update parameter";
+
+    @Activate
+    public void activate() {
+        eventExecutor = newSingleThreadScheduledExecutor(
+                groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+        vTapConsistentMap = storageService.<OpenstackVtapId, DefaultOpenstackVtap>
+                consistentMapBuilder()
+                .withName("vTapMap")
+                .withSerializer(SERIALIZER)
+                .build();
+
+        vTapMap = vTapConsistentMap.asJavaMap();
+        vTapConsistentMap.addListener(vTapListener);
+
+        vTapStatusListener = status -> {
+            if (status == Status.ACTIVE) {
+                eventExecutor.execute(this::loadVTapIds);
+            }
+        };
+        vTapConsistentMap.addStatusChangeListener(vTapStatusListener);
+
+        log.info("Started {} - {}", this.getClass().getSimpleName());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        vTapConsistentMap.removeStatusChangeListener(vTapStatusListener);
+        vTapConsistentMap.removeListener(vTapListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped {} - {}", this.getClass().getSimpleName());
+    }
+
+    private void loadVTapIds() {
+        vTapIdsByTxDeviceId.clear();
+        vTapIdsByRxDeviceId.clear();
+        vTapMap.values().forEach(vTap -> refreshDeviceIdsByVtap(null, vTap));
+    }
+
+    private boolean shouldUpdate(DefaultOpenstackVtap existing,
+                                 OpenstackVtap description,
+                                 boolean replaceDevices) {
+        if (existing == null) {
+            return true;
+        }
+
+        if ((description.type() != null && !description.type().equals(existing.type()))
+                || (description.vTapCriterion() != null &&
+                !description.vTapCriterion().equals(existing.vTapCriterion()))) {
+            return true;
+        }
+
+        if (description.txDeviceIds() != null) {
+            if (replaceDevices) {
+                if (!existing.txDeviceIds().equals(description.txDeviceIds())) {
+                    return true;
+                }
+            } else {
+                if (!existing.txDeviceIds().containsAll(description.txDeviceIds())) {
+                    return true;
+                }
+            }
+        }
+
+        if (description.rxDeviceIds() != null) {
+            if (replaceDevices) {
+                if (!existing.rxDeviceIds().equals(description.rxDeviceIds())) {
+                    return true;
+                }
+            } else {
+                if (!existing.rxDeviceIds().containsAll(description.rxDeviceIds())) {
+                    return true;
+                }
+            }
+        }
+
+        // check to see if any of the annotations provided by vTap
+        // differ from those in the existing vTap
+        return description.annotations().keys().stream()
+                .anyMatch(k -> !Objects.equals(description.annotations().value(k),
+                        existing.annotations().value(k)));
+    }
+
+    @Override
+    public OpenstackVtap createOrUpdateVtap(OpenstackVtapId vTapId,
+                                            OpenstackVtap description,
+                                            boolean replaceFlag) {
+
+        return vTapMap.compute(vTapId, (id, existing) -> {
+            if (existing == null &&
+                    (description.type() == null ||
+                     description.vTapCriterion() == null ||
+                     description.txDeviceIds() == null ||
+                     description.rxDeviceIds() == null)) {
+                checkState(false, INVALID_DESCRIPTION);
+                return null;
+            }
+
+            if (shouldUpdate(existing, description, replaceFlag)) {
+                // Replace items
+                OpenstackVtap.Type type =
+                        (description.type() == null ? existing.type() : description.type());
+                OpenstackVtapCriterion vTapCriterion =
+                        (description.vTapCriterion() == null ?
+                        existing.vTapCriterion() : description.vTapCriterion());
+
+                // Replace or add devices
+                Set<DeviceId> txDeviceIds;
+                if (description.txDeviceIds() == null) {
+                    txDeviceIds = existing.txDeviceIds();
+                } else {
+                    if (existing == null || replaceFlag) {
+                        txDeviceIds = ImmutableSet.copyOf(description.txDeviceIds());
+                    } else {
+                        txDeviceIds = Sets.newHashSet(existing.txDeviceIds());
+                        txDeviceIds.addAll(description.txDeviceIds());
+                    }
+                }
+
+                Set<DeviceId> rxDeviceIds;
+                if (description.rxDeviceIds() == null) {
+                    rxDeviceIds = existing.rxDeviceIds();
+                } else {
+                    if (existing == null || replaceFlag) {
+                        rxDeviceIds = ImmutableSet.copyOf(description.rxDeviceIds());
+                    } else {
+                        rxDeviceIds = Sets.newHashSet(existing.rxDeviceIds());
+                        rxDeviceIds.addAll(description.rxDeviceIds());
+                    }
+                }
+
+                // Replace or add annotations
+                SparseAnnotations annotations;
+                if (existing != null) {
+                    annotations = merge((DefaultAnnotations) existing.annotations(),
+                            (SparseAnnotations) description.annotations());
+                } else {
+                    annotations = (SparseAnnotations) description.annotations();
+                }
+
+                // Make new changed vTap and return
+                return DefaultOpenstackVtap.builder()
+                        .id(vTapId)
+                        .type(type)
+                        .vTapCriterion(vTapCriterion)
+                        .txDeviceIds(txDeviceIds)
+                        .rxDeviceIds(rxDeviceIds)
+                        .annotations(annotations)
+                        .build();
+            }
+            return existing;
+        });
+    }
+
+    @Override
+    public OpenstackVtap removeVtapById(OpenstackVtapId vTapId) {
+        return vTapMap.remove(vTapId);
+    }
+
+    @Override
+    public boolean addDeviceToVtap(OpenstackVtapId vTapId,
+                                   OpenstackVtap.Type type,
+                                   DeviceId deviceId) {
+        checkNotNull(vTapId);
+        checkNotNull(deviceId);
+
+        OpenstackVtap vTap = vTapMap.compute(vTapId, (id, existing) -> {
+            if (existing == null) {
+                return null;
+            }
+            if (!existing.type().isValid(type)) {
+                log.error("Not valid OpenstackVtap type {} for requested type {}",
+                        existing.type(), type);
+                return existing;
+            }
+
+            Set<DeviceId> txDeviceIds = null;
+            if (type.isValid(OpenstackVtap.Type.VTAP_TX) &&
+                    !existing.txDeviceIds().contains(deviceId)) {
+                txDeviceIds = Sets.newHashSet(existing.txDeviceIds());
+                txDeviceIds.add(deviceId);
+            }
+
+            Set<DeviceId> rxDeviceIds = null;
+            if (type.isValid(OpenstackVtap.Type.VTAP_RX) &&
+                    !existing.rxDeviceIds().contains(deviceId)) {
+                rxDeviceIds = Sets.newHashSet(existing.rxDeviceIds());
+                rxDeviceIds.add(deviceId);
+            }
+
+            if (txDeviceIds != null || rxDeviceIds != null) {
+                //updateVTapIdFromDeviceId(existing.id(), deviceId);    // execute from event listener
+
+                return DefaultOpenstackVtap.builder()
+                        .id(vTapId)
+                        .type(existing.type())
+                        .vTapCriterion(existing.vTapCriterion())
+                        .txDeviceIds(txDeviceIds != null ? txDeviceIds : existing.txDeviceIds())
+                        .rxDeviceIds(rxDeviceIds != null ? rxDeviceIds : existing.rxDeviceIds())
+                        .annotations(existing.annotations())
+                        .build();
+            }
+            return existing;
+        });
+        return (vTap != null);
+    }
+
+    @Override
+    public boolean removeDeviceFromVtap(OpenstackVtapId vTapId,
+                                        OpenstackVtap.Type type,
+                                        DeviceId deviceId) {
+        checkNotNull(vTapId);
+        checkNotNull(deviceId);
+
+        OpenstackVtap vTap = vTapMap.compute(vTapId, (id, existing) -> {
+            if (existing == null) {
+                return null;
+            }
+            if (!existing.type().isValid(type)) {
+                log.error("Not valid OpenstackVtap type {} for requested type {}",
+                        existing.type(), type);
+                return existing;
+            }
+
+            Set<DeviceId> txDeviceIds = null;
+            if (type.isValid(OpenstackVtap.Type.VTAP_TX) &&
+                    existing.txDeviceIds().contains(deviceId)) {
+                txDeviceIds = Sets.newHashSet(existing.txDeviceIds());
+                txDeviceIds.remove(deviceId);
+            }
+
+            Set<DeviceId> rxDeviceIds = null;
+            if (type.isValid(OpenstackVtap.Type.VTAP_RX) &&
+                    existing.rxDeviceIds().contains(deviceId)) {
+                rxDeviceIds = Sets.newHashSet(existing.rxDeviceIds());
+                rxDeviceIds.remove(deviceId);
+            }
+
+            if (txDeviceIds != null || rxDeviceIds != null) {
+                //removeVTapIdFromDeviceId(existing.id(), deviceId);    // execute from event listener
+
+                return DefaultOpenstackVtap.builder()
+                        .id(vTapId)
+                        .type(existing.type())
+                        .vTapCriterion(existing.vTapCriterion())
+                        .txDeviceIds(txDeviceIds != null ? txDeviceIds : existing.txDeviceIds())
+                        .rxDeviceIds(rxDeviceIds != null ? rxDeviceIds : existing.rxDeviceIds())
+                        .annotations(existing.annotations())
+                        .build();
+            }
+            return existing;
+        });
+        return (vTap != null);
+    }
+
+    @Override
+    public boolean updateDeviceForVtap(OpenstackVtapId vTapId,
+                                       Set<DeviceId> txDeviceIds, Set<DeviceId> rxDeviceIds,
+                                       boolean replaceDevices) {
+        checkNotNull(vTapId);
+        checkNotNull(txDeviceIds);
+        checkNotNull(rxDeviceIds);
+
+        OpenstackVtap vTap = vTapMap.compute(vTapId, (id, existing) -> {
+            if (existing == null) {
+                return null;
+            }
+
+            // Replace or add devices
+            Set<DeviceId> txDS = null;
+            if (replaceDevices) {
+                if (!existing.txDeviceIds().equals(txDeviceIds)) {
+                    txDS = ImmutableSet.copyOf(txDeviceIds);
+                }
+            } else {
+                if (!existing.txDeviceIds().containsAll(txDeviceIds)) {
+                    txDS = Sets.newHashSet(existing.txDeviceIds());
+                    txDS.addAll(txDeviceIds);
+                }
+            }
+
+            Set<DeviceId> rxDS = null;
+            if (replaceDevices) {
+                if (!existing.rxDeviceIds().equals(rxDeviceIds)) {
+                    rxDS = ImmutableSet.copyOf(rxDeviceIds);
+                }
+            } else {
+                if (!existing.rxDeviceIds().containsAll(rxDeviceIds)) {
+                    rxDS = Sets.newHashSet(existing.rxDeviceIds());
+                    rxDS.addAll(rxDeviceIds);
+                }
+            }
+
+            if (txDS != null || rxDS != null) {
+
+                return DefaultOpenstackVtap.builder()
+                        .id(vTapId)
+                        .type(existing.type())
+                        .vTapCriterion(existing.vTapCriterion())
+                        .txDeviceIds(txDS != null ? txDS : existing.txDeviceIds())
+                        .rxDeviceIds(rxDS != null ? rxDS : existing.rxDeviceIds())
+                        .annotations(existing.annotations())
+                        .build();
+            }
+            return existing;
+        });
+        return (vTap != null);
+    }
+
+    @Override
+    public int getVtapCount(OpenstackVtap.Type type) {
+        return (int) vTapMap.values().parallelStream()
+                .filter(vTap -> vTap.type().isValid(type))
+                .count();
+    }
+
+    @Override
+    public Set<OpenstackVtap> getVtaps(OpenstackVtap.Type type) {
+        return ImmutableSet.copyOf(
+                vTapMap.values().parallelStream()
+                        .filter(vTap -> vTap.type().isValid(type))
+                        .collect(Collectors.toSet()));
+    }
+
+    @Override
+    public OpenstackVtap getVtap(OpenstackVtapId vTapId) {
+        return vTapMap.get(vTapId);
+    }
+
+    @Override
+    public Set<OpenstackVtap> getVtapsByDeviceId(OpenstackVtap.Type type,
+                                                 DeviceId deviceId) {
+        Set<OpenstackVtapId> vtapIds = Sets.newHashSet();
+        if (type.isValid(OpenstackVtap.Type.VTAP_TX)) {
+            vtapIds.addAll(vTapIdsByTxDeviceId.get(deviceId));
+        }
+        if (type.isValid(OpenstackVtap.Type.VTAP_RX)) {
+            vtapIds.addAll(vTapIdsByRxDeviceId.get(deviceId));
+        }
+
+        return ImmutableSet.copyOf(
+                vtapIds.parallelStream()
+                        .map(vTapId -> vTapMap.get(vTapId))
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toSet()));
+    }
+
+    private class VTapComparator implements Comparator<OpenstackVtap> {
+        @Override
+        public int compare(OpenstackVtap v1, OpenstackVtap v2) {
+            int diff = (v2.type().compareTo(v1.type()));
+            if (diff == 0) {
+                return (v2.vTapCriterion().ipProtocol() - v1.vTapCriterion().ipProtocol());
+            }
+            return diff;
+        }
+    }
+
+    private static Set<OpenstackVtapId> addVTapIds(OpenstackVtapId vTapId) {
+        Set<OpenstackVtapId> vtapIds = Sets.newConcurrentHashSet();
+        vtapIds.add(vTapId);
+        return vtapIds;
+    }
+
+    private static Set<OpenstackVtapId> updateVTapIds(Set<OpenstackVtapId> existingVtapIds,
+                                                      OpenstackVtapId vTapId) {
+        existingVtapIds.add(vTapId);
+        return existingVtapIds;
+    }
+
+    private static Set<OpenstackVtapId> removeVTapIds(Set<OpenstackVtapId> existingVtapIds,
+                                                      OpenstackVtapId vTapId) {
+        existingVtapIds.remove(vTapId);
+        if (existingVtapIds.isEmpty()) {
+            return null;
+        }
+        return existingVtapIds;
+    }
+
+    private void updateVTapIdFromTxDeviceId(OpenstackVtapId vTapId, DeviceId deviceId) {
+        vTapIdsByTxDeviceId.compute(deviceId, (k, v) -> v == null ?
+                addVTapIds(vTapId) : updateVTapIds(v, vTapId));
+    }
+
+    private void removeVTapIdFromTxDeviceId(OpenstackVtapId vTapId, DeviceId deviceId) {
+        vTapIdsByTxDeviceId.computeIfPresent(deviceId, (k, v) -> removeVTapIds(v, vTapId));
+    }
+
+    private void updateVTapIdFromRxDeviceId(OpenstackVtapId vTapId, DeviceId deviceId) {
+        vTapIdsByRxDeviceId.compute(deviceId, (k, v) -> v == null ?
+                addVTapIds(vTapId) : updateVTapIds(v, vTapId));
+    }
+
+    private void removeVTapIdFromRxDeviceId(OpenstackVtapId vTapId, DeviceId deviceId) {
+        vTapIdsByRxDeviceId.computeIfPresent(deviceId, (k, v) -> removeVTapIds(v, vTapId));
+    }
+
+    private void refreshDeviceIdsByVtap(OpenstackVtap oldOpenstackVtap,
+                                        OpenstackVtap newOpenstackVtap) {
+        if (oldOpenstackVtap != null) {
+            Set<DeviceId> removeDeviceIds;
+
+            // Remove TX vTap
+            removeDeviceIds = (newOpenstackVtap != null) ?
+                    Sets.difference(oldOpenstackVtap.txDeviceIds(),
+                            newOpenstackVtap.txDeviceIds()) : oldOpenstackVtap.txDeviceIds();
+            removeDeviceIds.forEach(id -> removeVTapIdFromTxDeviceId(oldOpenstackVtap.id(), id));
+
+            // Remove RX vTap
+            removeDeviceIds = (newOpenstackVtap != null) ?
+                    Sets.difference(oldOpenstackVtap.rxDeviceIds(),
+                            newOpenstackVtap.rxDeviceIds()) : oldOpenstackVtap.rxDeviceIds();
+            removeDeviceIds.forEach(id -> removeVTapIdFromRxDeviceId(oldOpenstackVtap.id(), id));
+        }
+
+        if (newOpenstackVtap != null) {
+            Set<DeviceId> addDeviceIds;
+
+            // Add TX vTap
+            addDeviceIds = (oldOpenstackVtap != null) ?
+                    Sets.difference(newOpenstackVtap.txDeviceIds(),
+                            oldOpenstackVtap.txDeviceIds()) : newOpenstackVtap.txDeviceIds();
+            addDeviceIds.forEach(id -> updateVTapIdFromTxDeviceId(newOpenstackVtap.id(), id));
+
+            // Add RX vTap
+            addDeviceIds = (oldOpenstackVtap != null) ?
+                    Sets.difference(newOpenstackVtap.rxDeviceIds(),
+                            oldOpenstackVtap.rxDeviceIds()) : newOpenstackVtap.rxDeviceIds();
+            addDeviceIds.forEach(id -> updateVTapIdFromRxDeviceId(newOpenstackVtap.id(), id));
+        }
+    }
+
+    private class VTapEventListener
+            implements MapEventListener<OpenstackVtapId, DefaultOpenstackVtap> {
+        @Override
+        public void event(MapEvent<OpenstackVtapId, DefaultOpenstackVtap> event) {
+            DefaultOpenstackVtap newValue =
+                    event.newValue() != null ? event.newValue().value() : null;
+            DefaultOpenstackVtap oldValue =
+                    event.oldValue() != null ? event.oldValue().value() : null;
+
+            log.debug("VTapEventListener {} -> {}, {}", event.type(), oldValue, newValue);
+            switch (event.type()) {
+                case INSERT:
+                    refreshDeviceIdsByVtap(oldValue, newValue);
+                    notifyDelegate(new OpenstackVtapEvent(
+                            OpenstackVtapEvent.Type.VTAP_ADDED, newValue));
+                    break;
+
+                case UPDATE:
+                    if (!Objects.equals(newValue, oldValue)) {
+                        refreshDeviceIdsByVtap(oldValue, newValue);
+                        notifyDelegate(new OpenstackVtapEvent(
+                                OpenstackVtapEvent.Type.VTAP_UPDATED, newValue, oldValue));
+                    }
+                    break;
+
+                case REMOVE:
+                    refreshDeviceIdsByVtap(oldValue, newValue);
+                    notifyDelegate(new OpenstackVtapEvent(
+                            OpenstackVtapEvent.Type.VTAP_REMOVED, oldValue));
+                    break;
+
+                default:
+                    log.warn("Unknown map event type: {}", event.type());
+            }
+        }
+    }
+}
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java
index a7c4de7..fec6d18 100644
--- a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java
@@ -15,8 +15,17 @@
  */
 package org.onosproject.openstackvtap.impl;
 
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.VlanId;
+import org.onosproject.event.AbstractListenerManager;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.openstackvtap.api.OpenstackVtap;
+import org.onosproject.openstackvtap.api.OpenstackVtap.Type;
+import org.onosproject.openstackvtap.api.OpenstackVtapAdminService;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+import org.onosproject.openstackvtap.api.OpenstackVtapEvent;
 import org.onosproject.openstackvtap.api.OpenstackVtapId;
 import org.onosproject.openstackvtap.api.OpenstackVtapListener;
 import org.onosproject.openstackvtap.api.OpenstackVtapService;
@@ -24,17 +33,46 @@
 import java.util.Set;
 
 /**
- * Implementation of openstack vtap.
+ * Provides basic implementation of the user APIs.
  */
-public class OpenstackVtapManager implements OpenstackVtapService {
+@Component(immediate = true)
+@Service
+public class OpenstackVtapManager
+        extends AbstractListenerManager<OpenstackVtapEvent, OpenstackVtapListener>
+        implements OpenstackVtapService, OpenstackVtapAdminService {
 
     @Override
-    public int getVtapCount(OpenstackVtap.Type type) {
+    public OpenstackVtap createVtap(Type type, OpenstackVtapCriterion vTapCriterion) {
+        return null;
+    }
+
+    @Override
+    public OpenstackVtap updateVtap(OpenstackVtapId vTapId, OpenstackVtap vTap) {
+        return null;
+    }
+
+    @Override
+    public OpenstackVtap removeVtap(OpenstackVtapId vTapId) {
+        return null;
+    }
+
+    @Override
+    public void setVtapOutput(DeviceId deviceId, Type type, PortNumber portNumber, VlanId vlanId) {
+
+    }
+
+    @Override
+    public void setVtapOutput(DeviceId deviceId, Type type, PortNumber portNumber, int vni) {
+
+    }
+
+    @Override
+    public int getVtapCount(Type type) {
         return 0;
     }
 
     @Override
-    public Set<OpenstackVtap> getVtaps(OpenstackVtap.Type type) {
+    public Set<OpenstackVtap> getVtaps(Type type) {
         return null;
     }
 
@@ -44,15 +82,7 @@
     }
 
     @Override
-    public Set<OpenstackVtap> getVtapsByDeviceId(OpenstackVtap.Type type, DeviceId deviceId) {
+    public Set<OpenstackVtap> getVtapsByDeviceId(Type type, DeviceId deviceId) {
         return null;
     }
-
-    @Override
-    public void addListener(OpenstackVtapListener listener) {
-    }
-
-    @Override
-    public void removeListener(OpenstackVtapListener listener) {
-    }
 }