Add REST API for openstackvtap app

Change-Id: I5d7697069d804eeb88eb57cbd845af90764c7a86
diff --git a/apps/openstackvtap/api/src/main/java/org/onosproject/openstackvtap/api/Constants.java b/apps/openstackvtap/api/src/main/java/org/onosproject/openstackvtap/api/Constants.java
new file mode 100644
index 0000000..f823b7d
--- /dev/null
+++ b/apps/openstackvtap/api/src/main/java/org/onosproject/openstackvtap/api/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019-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.api;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Provides constants used in OpenstackVtap.
+ */
+public final class Constants {
+
+    private Constants() {
+    }
+
+    public static final String REST_UTF8 = StandardCharsets.UTF_8.name();
+}
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/cli/OpenstackVtapAddCommand.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/cli/OpenstackVtapAddCommand.java
index 013ac89..7fdd08e 100644
--- a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/cli/OpenstackVtapAddCommand.java
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/cli/OpenstackVtapAddCommand.java
@@ -19,15 +19,13 @@
 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.onlab.packet.IpPrefix;
-import org.onlab.packet.TpPort;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.openstackvtap.api.OpenstackVtap;
 import org.onosproject.openstackvtap.api.OpenstackVtapAdminService;
-import org.onosproject.openstackvtap.impl.DefaultOpenstackVtapCriterion;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
 
-import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getProtocolTypeFromString;
 import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getVtapTypeFromString;
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.makeVtapCriterion;
 
 /**
  * Adds a openstack vtap rule.
@@ -76,38 +74,19 @@
 
     @Override
     protected void doExecute() {
-        DefaultOpenstackVtapCriterion.Builder vtapCriterionBuilder = DefaultOpenstackVtapCriterion.builder();
-        if (makeCriterion(vtapCriterionBuilder)) {
-            OpenstackVtap.Type type = getVtapTypeFromString(vtapTypeStr);
-
-            if (type == null) {
-                print("Invalid vtap type");
-                return;
-            }
-
-            OpenstackVtap vtap = vtapService.createVtap(type, vtapCriterionBuilder.build());
-            if (vtap != null) {
-                print("Created OpenstackVtap with id { %s }", vtap.id().toString());
-            } else {
-                print("Failed to create OpenstackVtap");
-            }
-        }
-    }
-
-    private boolean makeCriterion(DefaultOpenstackVtapCriterion.Builder vtapCriterionBuilder) {
-        try {
-            vtapCriterionBuilder.srcIpPrefix(IpPrefix.valueOf(srcIp));
-            vtapCriterionBuilder.dstIpPrefix(IpPrefix.valueOf(dstIp));
-        } catch (Exception e) {
-            print("Inputted valid source IP & destination IP in CIDR (e.g., \"10.1.0.4/32\")");
-            return false;
+        OpenstackVtapCriterion criterion =
+                makeVtapCriterion(srcIp, dstIp, ipProto, srcTpPort, dstTpPort);
+        OpenstackVtap.Type type = getVtapTypeFromString(vtapTypeStr);
+        if (type == null) {
+            print("Invalid vtap type");
+            return;
         }
 
-        vtapCriterionBuilder.ipProtocol(getProtocolTypeFromString(ipProto.toLowerCase()));
-
-        vtapCriterionBuilder.srcTpPort(TpPort.tpPort(srcTpPort));
-        vtapCriterionBuilder.dstTpPort(TpPort.tpPort(dstTpPort));
-
-        return true;
+        OpenstackVtap vtap = vtapService.createVtap(type, criterion);
+        if (vtap != null) {
+            print("Created OpenstackVtap with id { %s }", vtap.id().toString());
+        } else {
+            print("Failed to create OpenstackVtap");
+        }
     }
 }
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionCodec.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionCodec.java
new file mode 100644
index 0000000..608362b
--- /dev/null
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionCodec.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019-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.codec;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+import org.onosproject.openstackvtap.impl.DefaultOpenstackVtapCriterion;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getProtocolStringFromType;
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getProtocolTypeFromString;
+
+/**
+ * Openstack vTap codec used for serializing and de-serializing JSON string.
+ */
+public final class OpenstackVtapCriterionCodec extends JsonCodec<OpenstackVtapCriterion> {
+
+    private static final String SRC_IP = "srcIp";
+    private static final String DST_IP = "dstIp";
+    private static final String IP_PROTOCOL = "ipProto";
+    private static final String SRC_PORT = "srcPort";
+    private static final String DST_PORT = "dstPort";
+
+    private static final String MISSING_MESSAGE = " is required in OpenstackVtapCriterion";
+
+    @Override
+    public ObjectNode encode(OpenstackVtapCriterion entity, CodecContext context) {
+        String protoStr = getProtocolStringFromType(entity.ipProtocol());
+
+        return context.mapper().createObjectNode()
+                .put(SRC_IP, entity.srcIpPrefix().address().toString())
+                .put(DST_IP, entity.dstIpPrefix().address().toString())
+                .put(IP_PROTOCOL, protoStr)
+                .put(SRC_PORT, entity.srcTpPort().toString())
+                .put(DST_PORT, entity.dstTpPort().toString());
+    }
+
+    @Override
+    public OpenstackVtapCriterion decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        OpenstackVtapCriterion.Builder cBuilder = DefaultOpenstackVtapCriterion.builder();
+
+        // parse source IP address
+        IpPrefix srcIp = IpPrefix.valueOf(IpAddress.valueOf(nullIsIllegal(
+                json.get(SRC_IP).asText(), SRC_IP + MISSING_MESSAGE)), 32);
+        // parse destination IP address
+        IpPrefix dstIp = IpPrefix.valueOf(IpAddress.valueOf(nullIsIllegal(
+                json.get(DST_IP).asText(), DST_IP + MISSING_MESSAGE)), 32);
+
+        cBuilder.srcIpPrefix(srcIp);
+        cBuilder.dstIpPrefix(dstIp);
+
+        // parse IP protocol
+        String ipProtoStr = json.get(IP_PROTOCOL).asText();
+        if (ipProtoStr != null) {
+            cBuilder.ipProtocol(getProtocolTypeFromString(ipProtoStr));
+        }
+
+        // parse source port number
+        int srcPort = json.get(SRC_PORT).asInt(0);
+
+        // parse destination port number
+        int dstPort = json.get(DST_PORT).asInt(0);
+
+        cBuilder.srcTpPort(TpPort.tpPort(srcPort));
+        cBuilder.dstTpPort(TpPort.tpPort(dstPort));
+
+        return cBuilder.build();
+    }
+}
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/util/OpenstackVtapUtil.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/util/OpenstackVtapUtil.java
index 7950bee..6907d4b 100644
--- a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/util/OpenstackVtapUtil.java
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/util/OpenstackVtapUtil.java
@@ -18,6 +18,7 @@
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
 import org.onosproject.net.Host;
 import org.onosproject.net.behaviour.TunnelDescription;
 import org.onosproject.net.group.DefaultGroupKey;
@@ -25,7 +26,9 @@
 import org.onosproject.openstackvtap.api.OpenstackVtap;
 import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
 import org.onosproject.openstackvtap.api.OpenstackVtapNetwork;
+import org.onosproject.openstackvtap.impl.DefaultOpenstackVtapCriterion;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayOutputStream;
 import java.nio.charset.StandardCharsets;
@@ -38,6 +41,8 @@
  */
 public final class OpenstackVtapUtil {
 
+    private static final Logger log = LoggerFactory.getLogger(OpenstackVtapUtil.class);
+
     private static final String VTAP_TUNNEL_GRE = "vtap_gre";
     private static final String VTAP_TUNNEL_VXLAN = "vtap_vxlan";
     private static final String VTAP_GROUP_KEY = "VTAP_GROUP_KEY";
@@ -70,6 +75,27 @@
     }
 
     /**
+     * Obtains IP protocol string from the given type.
+     *
+     * @param type protocol type
+     * @return IP protocol string
+     */
+    public static String getProtocolStringFromType(byte type) {
+        switch (type) {
+            case IPv4.PROTOCOL_TCP:
+                return "tcp";
+            case IPv4.PROTOCOL_UDP:
+                return "udp";
+            case IPv4.PROTOCOL_ICMP:
+                return "icmp";
+            case 0:
+                return "any";
+            default:
+                throw new IllegalArgumentException("Invalid vtap protocol type");
+        }
+    }
+
+    /**
      * Obtains openstack vtap type from the given string.
      *
      * @param str vtap type string
@@ -179,6 +205,38 @@
     }
 
     /**
+     * Makes Openstack vTap criterion from the given src, dst IP and port.
+     *
+     * @param srcIp     source IP address
+     * @param dstIp     destination IP address
+     * @param ipProto   IP protocol
+     * @param srcPort   source port
+     * @param dstPort   destination port
+     * @return openstack vTap criterion
+     */
+    public static OpenstackVtapCriterion makeVtapCriterion(String srcIp,
+                                                           String dstIp,
+                                                           String ipProto,
+                                                           int srcPort,
+                                                           int dstPort) {
+        OpenstackVtapCriterion.Builder cBuilder = DefaultOpenstackVtapCriterion.builder();
+
+        try {
+            cBuilder.srcIpPrefix(IpPrefix.valueOf(srcIp));
+            cBuilder.dstIpPrefix(IpPrefix.valueOf(dstIp));
+        } catch (Exception e) {
+            log.error("The given IP addresses are invalid");
+         }
+
+        cBuilder.ipProtocol(getProtocolTypeFromString(ipProto.toLowerCase()));
+
+        cBuilder.srcTpPort(TpPort.tpPort(srcPort));
+        cBuilder.dstTpPort(TpPort.tpPort(dstPort));
+
+        return cBuilder.build();
+    }
+
+    /**
      * Print stack trace of given exception.
      *
      * @param log logger for using print
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapNetworkCodecRegister.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapNetworkCodecRegister.java
index 509a286..a318320 100644
--- a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapNetworkCodecRegister.java
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapNetworkCodecRegister.java
@@ -19,7 +19,9 @@
 
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.onosproject.codec.CodecService;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
 import org.onosproject.openstackvtap.api.OpenstackVtapNetwork;
+import org.onosproject.openstackvtap.codec.OpenstackVtapCriterionCodec;
 import org.onosproject.openstackvtap.codec.OpenstackVtapNetworkCodec;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
@@ -44,6 +46,7 @@
     @Activate
     protected void activate() {
         codecService.registerCodec(OpenstackVtapNetwork.class, new OpenstackVtapNetworkCodec());
+        codecService.registerCodec(OpenstackVtapCriterion.class, new OpenstackVtapCriterionCodec());
 
         log.info("Started");
     }
@@ -51,6 +54,7 @@
     @Deactivate
     protected void deactivate() {
         codecService.unregisterCodec(OpenstackVtapNetwork.class);
+        codecService.unregisterCodec(OpenstackVtapCriterion.class);
 
         log.info("Stopped");
     }
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapWebResource.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapWebResource.java
index f4c3084..5021887 100644
--- a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapWebResource.java
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/web/OpenstackVtapWebResource.java
@@ -15,14 +15,38 @@
  */
 package org.onosproject.openstackvtap.web;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Sets;
+import org.onosproject.openstackvtap.api.OpenstackVtap;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+import org.onosproject.openstackvtap.api.OpenstackVtapId;
+import org.onosproject.openstackvtap.api.OpenstackVtapService;
 import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+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.InputStream;
+import java.util.Set;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.onlab.util.Tools.readTreeFromStream;
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getVtapTypeFromString;
 
 /**
  * Handles REST API call of openstack vtap.
@@ -30,16 +54,119 @@
 @Path("vtap")
 public class OpenstackVtapWebResource extends AbstractWebResource {
 
-    private final ObjectNode root = mapper().createObjectNode();
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String MESSAGE_VTAP = "Received vtap %s request";
+    private static final String CREATE = "CREATE";
+    private static final String QUERY = "QUERY";
+    private static final String DELETE = "DELETE";
+
+    private static final String VTAP = "vtap";
+    private static final String CRITERION = "criterion";
+    private static final String TYPE = "type";
+    private static final String ID = "id";
+
+    private final ObjectNode  root = mapper().createObjectNode();
+    private final ArrayNode jsonVtaps = root.putArray("vtaps");
+
+    private final OpenstackVtapService vtapService = get(OpenstackVtapService.class);
+
+    @Context
+    private UriInfo uriInfo;
 
     /**
-     * OpenstackVtapServiceImpl method.
+     * Creates an openstack vTap from the JSON input stream.
      *
-     * @return 200 OK
+     * @param input openstack vtap JSON input stream
+     * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+     * is invalid or duplicated vtap already exists
+     *
+     * @onos.rsModel OpenstackVtap
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response createVtap(InputStream input) {
+        log.trace(String.format(MESSAGE_VTAP, CREATE));
+
+        OpenstackVtap vtap = readAndCreateVtap(input);
+
+        UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+                .path(VTAP)
+                .path(vtap.id().toString());
+
+        return created(locationBuilder.build()).build();
+    }
+
+    /**
+     * Removes an openstack vTap with the given vTap UUID.
+     *
+     * @param id openstack vtap UUID
+     * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed
+     */
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}")
+    public Response deleteVtap(@PathParam(ID) String id) {
+        log.trace(String.format(MESSAGE_VTAP, DELETE + id));
+
+        vtapService.removeVtap(OpenstackVtapId.vtapId(id));
+        return noContent().build();
+    }
+
+    /**
+     * Gets openstack vtap entities.
+     *
+     * @return 200 OK with openstack vtap entities
+     *         404 NOT_FOUND if openstack vtap does not exist
      */
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    public Response dummy() {
+    public Response getVtaps() {
+        log.trace(String.format(MESSAGE_VTAP, QUERY));
+
+        Set<OpenstackVtap> allVtaps = vtapService.getVtaps(OpenstackVtap.Type.VTAP_ALL);
+        Set<OpenstackVtap> txVtaps = vtapService.getVtaps(OpenstackVtap.Type.VTAP_TX);
+        Set<OpenstackVtap> rxVtaps = vtapService.getVtaps(OpenstackVtap.Type.VTAP_RX);
+        Set<OpenstackVtap> anyVtaps = vtapService.getVtaps(OpenstackVtap.Type.VTAP_ANY);
+
+        Set<OpenstackVtap> vTaps = Sets.newConcurrentHashSet();
+        vTaps.addAll(allVtaps);
+        vTaps.addAll(txVtaps);
+        vTaps.addAll(rxVtaps);
+        vTaps.addAll(anyVtaps);
+
+        for (OpenstackVtap vtap : vTaps) {
+            ObjectNode json = mapper().createObjectNode();
+            json.set(CRITERION, codec(OpenstackVtapCriterion.class)
+                    .encode(vtap.vtapCriterion(), this));
+            json.put(TYPE, vtap.type().name());
+            json.put(ID, vtap.id().toString());
+
+            jsonVtaps.add(json);
+        }
+
         return ok(root).build();
     }
+
+    private OpenstackVtap readAndCreateVtap(InputStream input) {
+        OpenstackVtap.Type type = OpenstackVtap.Type.VTAP_ALL;
+        OpenstackVtapCriterion criterion;
+        try {
+            JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
+            String typeStr = jsonTree.path(TYPE).asText();
+
+            if (typeStr != null) {
+                type = getVtapTypeFromString(typeStr);
+            }
+
+            ObjectNode jsonCriterion = jsonTree.path(CRITERION).deepCopy();
+            criterion = codec(OpenstackVtapCriterion.class).decode(jsonCriterion, this);
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+
+        return vtapService.createVtap(type, criterion);
+    }
 }
diff --git a/apps/openstackvtap/app/src/main/resources/definitions/OpenstackVtap.json b/apps/openstackvtap/app/src/main/resources/definitions/OpenstackVtap.json
new file mode 100644
index 0000000..4cd7c9e
--- /dev/null
+++ b/apps/openstackvtap/app/src/main/resources/definitions/OpenstackVtap.json
@@ -0,0 +1,45 @@
+{
+  "type": "object",
+  "required": [
+    "type",
+    "criterion"
+  ],
+  "properties": {
+    "type": {
+      "type": "string",
+      "example": "all"
+    },
+    "criterion": {
+      "type": "object",
+      "required": [
+        "srcIp",
+        "dstIp",
+        "ipProto",
+        "srcPort",
+        "dstPort"
+      ],
+      "properties": {
+        "srcIp": {
+          "type": "string",
+          "example": "10.20.1.1"
+        },
+        "dstIp": {
+          "type": "String",
+          "example": "10.20.1.2"
+        },
+        "ipProto": {
+          "type": "string",
+          "example": "tcp"
+        },
+        "srcPort": {
+          "type": "integer",
+          "example": 8080
+        },
+        "dstPort": {
+          "type": "integer",
+          "example": 9090
+        }
+      }
+    }
+  }
+}
diff --git a/apps/openstackvtap/app/src/test/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionCodecTest.java b/apps/openstackvtap/app/src/test/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionCodecTest.java
new file mode 100644
index 0000000..c647a73
--- /dev/null
+++ b/apps/openstackvtap/app/src/test/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionCodecTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2019-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.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.MatcherAssert;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+import org.onosproject.openstackvtap.impl.DefaultOpenstackVtapCriterion;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.net.NetTestTools.APP_ID;
+import static org.onosproject.openstackvtap.codec.OpenstackVtapCriterionJsonMatcher.matchVtapCriterion;
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getProtocolStringFromType;
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getProtocolTypeFromString;
+
+/**
+ * Unit tests for OpenstackVtapCriterion codec.
+ */
+public class OpenstackVtapCriterionCodecTest {
+
+    MockCodecContext context;
+    JsonCodec<OpenstackVtapCriterion> vtapCriterionCodec;
+
+    final CoreService mockCoreService = createMock(CoreService.class);
+    private static final String REST_APP_ID = "org.onosproject.rest";
+
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        vtapCriterionCodec = new OpenstackVtapCriterionCodec();
+
+        assertThat(vtapCriterionCodec, notNullValue());
+
+        expect(mockCoreService.registerApplication(REST_APP_ID))
+                .andReturn(APP_ID).anyTimes();
+        replay(mockCoreService);
+        context.registerService(CoreService.class, mockCoreService);
+    }
+
+    /**
+     * Tests the openstack vtap criterion encoding.
+     */
+    @Test
+    public void testOpenstackVtapCriterionEncode() {
+        OpenstackVtapCriterion criterion = DefaultOpenstackVtapCriterion.builder()
+                .srcIpPrefix(IpPrefix.valueOf(IpAddress.valueOf("10.10.10.10"), 32))
+                .dstIpPrefix(IpPrefix.valueOf(IpAddress.valueOf("20.20.20.20"), 32))
+                .ipProtocol(getProtocolTypeFromString("tcp"))
+                .srcTpPort(TpPort.tpPort(8080))
+                .dstTpPort(TpPort.tpPort(9090))
+                .build();
+
+        ObjectNode criterionJson = vtapCriterionCodec.encode(criterion, context);
+        assertThat(criterionJson, matchVtapCriterion(criterion));
+    }
+
+    /**
+     * Tests the openstack vtap criterion decoding.
+     */
+    @Test
+    public void testOpenstackVtapCriterionDecode() throws IOException {
+        OpenstackVtapCriterion criterion = getVtapCriterion("OpenstackVtapCriterion.json");
+
+        assertThat(criterion.srcIpPrefix().address().toString(), is("10.10.10.10"));
+        assertThat(criterion.dstIpPrefix().address().toString(), is("20.20.20.20"));
+        assertThat(getProtocolStringFromType(criterion.ipProtocol()), is("tcp"));
+        assertThat(criterion.srcTpPort().toInt(), is(8080));
+        assertThat(criterion.dstTpPort().toInt(), is(9090));
+    }
+
+    /**
+     * Reads in an openstack vtap criterion from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded openstack vtap criterion
+     * @throws IOException if processing the resource fails
+     */
+    private OpenstackVtapCriterion getVtapCriterion(String resourceName) throws IOException {
+        InputStream jsonStream = OpenstackVtapCriterionCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        MatcherAssert.assertThat(json, notNullValue());
+        OpenstackVtapCriterion criterion = vtapCriterionCodec.decode((ObjectNode) json, context);
+        assertThat(criterion, notNullValue());
+        return criterion;
+    }
+
+    /**
+     * Mock codec context for use in codec unit tests.
+     */
+    private class MockCodecContext implements CodecContext {
+        private final ObjectMapper mapper = new ObjectMapper();
+        private final CodecManager manager = new CodecManager();
+        private final Map<Class<?>, Object> services = new HashMap<>();
+
+        /**
+         * Constructs a new mock codec context.
+         */
+        public MockCodecContext() {
+            manager.activate();
+        }
+
+        @Override
+        public ObjectMapper mapper() {
+            return mapper;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> JsonCodec<T> codec(Class<T> entityClass) {
+            if (entityClass == OpenstackVtapCriterion.class) {
+                return (JsonCodec<T>) vtapCriterionCodec;
+            }
+            return manager.getCodec(entityClass);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T getService(Class<T> serviceClass) {
+            return (T) services.get(serviceClass);
+        }
+
+        // for registering mock services
+        public <T> void registerService(Class<T> serviceClass, T impl) {
+            services.put(serviceClass, impl);
+        }
+    }
+}
diff --git a/apps/openstackvtap/app/src/test/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionJsonMatcher.java b/apps/openstackvtap/app/src/test/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionJsonMatcher.java
new file mode 100644
index 0000000..5c65263
--- /dev/null
+++ b/apps/openstackvtap/app/src/test/java/org/onosproject/openstackvtap/codec/OpenstackVtapCriterionJsonMatcher.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2019-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.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.openstackvtap.api.OpenstackVtapCriterion;
+
+import static org.onosproject.openstackvtap.util.OpenstackVtapUtil.getProtocolStringFromType;
+
+/**
+ * Hamcrest matcher for openstack vtap criterion config.
+ */
+public final class OpenstackVtapCriterionJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+    private final OpenstackVtapCriterion criterion;
+
+    private static final String SRC_IP = "srcIp";
+    private static final String DST_IP = "dstIp";
+    private static final String IP_PROTOCOL = "ipProto";
+    private static final String SRC_PORT = "srcPort";
+    private static final String DST_PORT = "dstPort";
+
+    private OpenstackVtapCriterionJsonMatcher(OpenstackVtapCriterion criterion) {
+        this.criterion = criterion;
+    }
+
+    @Override
+    protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+        // check source IP address
+        JsonNode jsonSrcIp = jsonNode.get(SRC_IP);
+        String srcIp = criterion.srcIpPrefix().address().toString();
+        if (!jsonSrcIp.asText().equals(srcIp)) {
+            description.appendText("Source IP address was " + jsonSrcIp);
+            return false;
+        }
+
+        // check destination IP address
+        JsonNode jsonDstIp = jsonNode.get(DST_IP);
+        String dstIp = criterion.dstIpPrefix().address().toString();
+        if (!jsonDstIp.asText().equals(dstIp)) {
+            description.appendText("Destination IP address was " + jsonDstIp);
+            return false;
+        }
+
+        // check IP protocol
+        JsonNode jsonIpProto = jsonNode.get(IP_PROTOCOL);
+        if (jsonIpProto != null) {
+            String ipProto = getProtocolStringFromType(criterion.ipProtocol());
+            if (!jsonIpProto.asText().equals(ipProto)) {
+                description.appendText("IP protocol was " + jsonIpProto);
+                return false;
+            }
+        }
+
+        // check source port number
+        JsonNode jsonSrcPort = jsonNode.get(SRC_PORT);
+        if (jsonSrcPort != null) {
+            int srcPort = criterion.srcTpPort().toInt();
+            if (jsonSrcPort.asInt() != srcPort) {
+                description.appendText("Source port number was " + jsonSrcPort);
+                return false;
+            }
+        }
+
+        // check destination port number
+        JsonNode jsonDstPort = jsonNode.get(DST_PORT);
+        if (jsonDstPort != null) {
+            int dstPort = criterion.dstTpPort().toInt();
+            if (jsonDstPort.asInt() != dstPort) {
+                description.appendText("Destination port number was " + jsonDstPort);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText(criterion.toString());
+    }
+
+    /**
+     * Factory to allocate openstack vtap criterion matcher.
+     *
+     * @param criterion vtap criterion object we are looking for
+     * @return matcher
+     */
+    public static OpenstackVtapCriterionJsonMatcher matchVtapCriterion(OpenstackVtapCriterion criterion) {
+        return new OpenstackVtapCriterionJsonMatcher(criterion);
+    }
+}
diff --git a/apps/openstackvtap/app/src/test/resources/org/onosproject/openstackvtap/codec/OpenstackVtapCriterion.json b/apps/openstackvtap/app/src/test/resources/org/onosproject/openstackvtap/codec/OpenstackVtapCriterion.json
new file mode 100644
index 0000000..3a5452b
--- /dev/null
+++ b/apps/openstackvtap/app/src/test/resources/org/onosproject/openstackvtap/codec/OpenstackVtapCriterion.json
@@ -0,0 +1,7 @@
+{
+  "srcIp": "10.10.10.10",
+  "dstIp": "20.20.20.20",
+  "ipProto": "tcp",
+  "srcPort": 8080,
+  "dstPort": 9090
+}
\ No newline at end of file