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