[ONOS-4530] Allow to specify appId when insert FlowRule through REST
- Augment FlowRuleCodec to encode FlowRule
- Add unit test for encode method of FlowRuleCodec
- Add getFlowByAppId and removeFlowByAppId methods in FlowsWebResource
- Add more unit tests for FlowWebResource
- Add FlowRules.json swagger doc
- Rename Flows.json to FlowEntries.json, correct FlowEntries.json
Change-Id: Ic3ec390c13a349e51ae4208adbc478564b6724ba
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
index e36fe89f..1a41e5c 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
@@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.flow.DefaultFlowRule;
@@ -26,6 +27,7 @@
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
+import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.util.Tools.nullIsIllegal;
/**
@@ -36,14 +38,46 @@
private static final String PRIORITY = "priority";
private static final String TIMEOUT = "timeout";
private static final String IS_PERMANENT = "isPermanent";
+ private static final String APP_ID = "appId";
private static final String TABLE_ID = "tableId";
private static final String DEVICE_ID = "deviceId";
private static final String TREATMENT = "treatment";
private static final String SELECTOR = "selector";
private static final String MISSING_MEMBER_MESSAGE =
- " member is required in FlowRule";
+ " member is required in FlowRule";
public static final String REST_APP_ID = "org.onosproject.rest";
+ @Override
+ public ObjectNode encode(FlowRule flowRule, CodecContext context) {
+ checkNotNull(flowRule, "Flow rule cannot be null");
+
+ CoreService service = context.getService(CoreService.class);
+ ApplicationId appId = service.getAppId(flowRule.appId());
+ String strAppId = (appId == null) ? "<none>" : appId.name();
+
+ final ObjectNode result = context.mapper().createObjectNode()
+ .put("id", Long.toString(flowRule.id().value()))
+ .put("tableId", flowRule.tableId())
+ .put("appId", strAppId)
+ .put("priority", flowRule.priority())
+ .put("timeout", flowRule.timeout())
+ .put("isPermanent", flowRule.isPermanent())
+ .put("deviceId", flowRule.deviceId().toString());
+
+ if (flowRule.treatment() != null) {
+ final JsonCodec<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ result.set("treatment", treatmentCodec.encode(flowRule.treatment(), context));
+ }
+
+ if (flowRule.selector() != null) {
+ final JsonCodec<TrafficSelector> selectorCodec =
+ context.codec(TrafficSelector.class);
+ result.set("selector", selectorCodec.encode(flowRule.selector(), context));
+ }
+
+ return result;
+ }
@Override
public FlowRule decode(ObjectNode json, CodecContext context) {
@@ -54,8 +88,9 @@
FlowRule.Builder resultBuilder = new DefaultFlowRule.Builder();
CoreService coreService = context.getService(CoreService.class);
- resultBuilder.fromApp(coreService
- .registerApplication(REST_APP_ID));
+ JsonNode appIdJson = json.get(APP_ID);
+ String appId = appIdJson != null ? appIdJson.asText() : REST_APP_ID;
+ resultBuilder.fromApp(coreService.registerApplication(appId));
int priority = nullIsIllegal(json.get(PRIORITY),
PRIORITY + MISSING_MEMBER_MESSAGE).asInt();
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
index 9c5e3fd..11b7eb6 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
@@ -17,6 +17,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.EthType;
@@ -29,12 +31,14 @@
import org.onosproject.codec.JsonCodec;
import org.onosproject.core.CoreService;
import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.DeviceId;
import org.onosproject.net.GridType;
import org.onosproject.net.Lambda;
import org.onosproject.net.OchSignal;
import org.onosproject.net.OchSignalType;
import org.onosproject.net.OduSignalType;
import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.EthCriterion;
@@ -75,6 +79,7 @@
import java.util.SortedMap;
import java.util.TreeMap;
+import static org.easymock.EasyMock.anyShort;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
@@ -105,6 +110,7 @@
expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
.andReturn(APP_ID).anyTimes();
+ expect(mockCoreService.getAppId(anyShort())).andReturn(APP_ID).anyTimes();
replay(mockCoreService);
context.registerService(CoreService.class, mockCoreService);
}
@@ -166,6 +172,118 @@
SortedMap<String, Instruction> instructions = new TreeMap<>();
/**
+ * Checks that a simple rule encodes properly.
+ */
+ @Test
+ public void testFlowRuleEncode() {
+
+ DeviceId deviceId = DeviceId.deviceId("of:000000000000000a");
+ FlowRule permFlowRule = DefaultFlowRule.builder()
+ .withCookie(1)
+ .forTable(1)
+ .withPriority(1)
+ .makePermanent()
+ .forDevice(deviceId).build();
+
+ FlowRule tempFlowRule = DefaultFlowRule.builder()
+ .withCookie(1)
+ .forTable(1)
+ .withPriority(1)
+ .makeTemporary(1000)
+ .forDevice(deviceId).build();
+
+ ObjectNode permFlowRuleJson = flowRuleCodec.encode(permFlowRule, context);
+ ObjectNode tempFlowRuleJson = flowRuleCodec.encode(tempFlowRule, context);
+
+ assertThat(permFlowRuleJson, FlowRuleJsonMatcher.matchesFlowRule(permFlowRule));
+ assertThat(tempFlowRuleJson, FlowRuleJsonMatcher.matchesFlowRule(tempFlowRule));
+ }
+
+ private static final class FlowRuleJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final FlowRule flowRule;
+
+ private FlowRuleJsonMatcher(FlowRule flowRule) {
+ this.flowRule = flowRule;
+ }
+
+ @Override
+ protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+ // check id
+ long jsonId = jsonNode.get("id").asLong();
+ long id = flowRule.id().id();
+ if (jsonId != id) {
+ description.appendText("flow rule id was " + jsonId);
+ return false;
+ }
+
+ // TODO: need to check application ID
+
+ // check tableId
+ int jsonTableId = jsonNode.get("tableId").asInt();
+ int tableId = flowRule.tableId();
+ if (jsonTableId != tableId) {
+ description.appendText("table id was " + jsonId);
+ return false;
+ }
+
+ // check priority
+ int jsonPriority = jsonNode.get("priority").asInt();
+ int priority = flowRule.priority();
+ if (jsonPriority != priority) {
+ description.appendText("priority was " + jsonPriority);
+ return false;
+ }
+
+ // check timeout
+ int jsonTimeout = jsonNode.get("timeout").asInt();
+ int timeout = flowRule.timeout();
+ if (jsonTimeout != timeout) {
+ description.appendText("timeout was " + jsonTimeout);
+ return false;
+ }
+
+ // check isPermanent
+ boolean jsonIsPermanent = jsonNode.get("isPermanent").asBoolean();
+ boolean isPermanent = flowRule.isPermanent();
+ if (jsonIsPermanent != isPermanent) {
+ description.appendText("isPermanent was " + jsonIsPermanent);
+ return false;
+ }
+
+ // check deviceId
+ String jsonDeviceId = jsonNode.get("deviceId").asText();
+ String deviceId = flowRule.deviceId().toString();
+ if (!jsonDeviceId.equals(deviceId)) {
+ description.appendText("deviceId was " + jsonDeviceId);
+ return false;
+ }
+
+ // TODO: need to check traffic treatment
+
+ // TODO: need to check selector
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(flowRule.toString());
+ }
+
+ /**
+ * Factory to allocate a flow rule matcher.
+ *
+ * @param flowRule flow rule object we are looking for
+ * @return matcher
+ */
+ public static FlowRuleJsonMatcher matchesFlowRule(FlowRule flowRule) {
+ return new FlowRuleJsonMatcher(flowRule);
+ }
+ }
+
+ /**
* Looks up an instruction in the instruction map based on type and subtype.
*
* @param type type string
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
index fc99c49..42e9590 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
@@ -21,6 +21,8 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.onlab.util.ItemNotFoundException;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.core.ApplicationId;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
@@ -36,6 +38,7 @@
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -61,6 +64,7 @@
private static final String DEVICE_NOT_FOUND = "Device is not found";
private static final String FLOW_NOT_FOUND = "Flow is not found";
+ private static final String APP_ID_NOT_FOUND = "Application Id is not found";
private static final String FLOWS = "flows";
private static final String DEVICE_ID = "deviceId";
private static final String FLOW_ID = "flowId";
@@ -73,7 +77,7 @@
* Gets all flow entries. Returns array of all flow rules in the system.
*
* @return 200 OK with a collection of flows
- * @onos.rsModel Flows
+ * @onos.rsModel FlowEntries
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@@ -107,10 +111,15 @@
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- public Response createFlows(InputStream stream) {
+ public Response createFlows(@QueryParam("appId") String appId, InputStream stream) {
try {
ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
ArrayNode flowsArray = (ArrayNode) jsonTree.get(FLOWS);
+
+ if (appId != null) {
+ flowsArray.forEach(flowJson -> ((ObjectNode) flowJson).put("appId", appId));
+ }
+
List<FlowRule> rules = codec(FlowRule.class).decode(flowsArray, this);
service.applyFlowRules(rules.toArray(new FlowRule[rules.size()]));
rules.forEach(flowRule -> {
@@ -131,10 +140,11 @@
*
* @param deviceId device identifier
* @return 200 OK with a collection of flows of given device
- * @onos.rsModel Flows
+ * @onos.rsModel FlowEntries
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
+ // TODO: we need to add "/device" suffix to the path to differentiate with appId
@Path("{deviceId}")
public Response getFlowByDeviceId(@PathParam("deviceId") String deviceId) {
final Iterable<FlowEntry> flowEntries =
@@ -150,13 +160,13 @@
}
/**
- * Gets flow rule. Returns the flow entry specified by the device id and
+ * Gets flow rules. Returns the flow entry specified by the device id and
* flow rule id.
*
* @param deviceId device identifier
* @param flowId flow rule identifier
- * @return 200 OK with a flows of given device and flow
- * @onos.rsModel Flows
+ * @return 200 OK with a collection of flows of given device and flow
+ * @onos.rsModel FlowEntries
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@@ -178,6 +188,43 @@
}
/**
+ * Gets flow rules generated by an application.
+ * Returns the flow rule specified by the application id.
+ *
+ * @param appId application identifier
+ * @return 200 OK with a collection of flows of given application id
+ * @onos.rsModel FlowRules
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("application/{appId}")
+ public Response getFlowByAppId(@PathParam("appId") String appId) {
+ final ApplicationService appService = get(ApplicationService.class);
+ final ApplicationId idInstant = nullIsNotFound(appService.getId(appId), APP_ID_NOT_FOUND);
+ final Iterable<FlowRule> flowRules = service.getFlowRulesById(idInstant);
+
+ flowRules.forEach(flow -> flowsNode.add(codec(FlowRule.class).encode(flow, this)));
+ return ok(root).build();
+ }
+
+ /**
+ * Removes flow rules by application ID.
+ * Removes a collection of flow rules generated by the given application.
+ *
+ * @param appId application identifier
+ * @return 204 NO CONTENT
+ */
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("application/{appId}")
+ public Response removeFlowByAppId(@PathParam("appId") String appId) {
+ final ApplicationService appService = get(ApplicationService.class);
+ final ApplicationId idInstant = nullIsNotFound(appService.getId(appId), APP_ID_NOT_FOUND);
+ service.removeFlowRulesById(idInstant);
+ return Response.noContent().build();
+ }
+
+ /**
* Creates new flow rule. Creates and installs a new flow rule for the
* specified device. <br>
* Instructions description:
@@ -187,6 +234,7 @@
* https://wiki.onosproject.org/display/ONOS/Flow+Rule+Criteria
*
* @param deviceId device identifier
+ * @param appId application identifier
* @param stream flow rule JSON
* @return status of the request - CREATED if the JSON is correct,
* BAD_REQUEST if the JSON is invalid
@@ -197,6 +245,7 @@
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createFlow(@PathParam("deviceId") String deviceId,
+ @QueryParam("appId") String appId,
InputStream stream) {
try {
ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
@@ -207,6 +256,11 @@
"Invalid deviceId in flow creation request");
}
jsonTree.put("deviceId", deviceId);
+
+ if (appId != null) {
+ jsonTree.put("appId", appId);
+ }
+
FlowRule rule = codec(FlowRule.class).decode(jsonTree, this);
service.applyFlowRules(rule);
UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
@@ -223,7 +277,7 @@
}
/**
- * Remove flow rule. Removes the specified flow rule.
+ * Removes flow rule. Removes the specified flow rule.
*
* @param deviceId device identifier
* @param flowId flow rule identifier
diff --git a/web/api/src/main/resources/definitions/FlowEntries.json b/web/api/src/main/resources/definitions/FlowEntries.json
new file mode 100644
index 0000000..15f8628
--- /dev/null
+++ b/web/api/src/main/resources/definitions/FlowEntries.json
@@ -0,0 +1,373 @@
+{
+ "type": "object",
+ "title": "flows",
+ "required": [
+ "flows"
+ ],
+ "properties": {
+ "flows": {
+ "type": "array",
+ "xml": {
+ "name": "flows",
+ "wrapped": true
+ },
+ "items": {
+ "type": "object",
+ "title": "flow",
+ "required": [
+ "id",
+ "tableId",
+ "appId",
+ "groupId",
+ "priority",
+ "timeout",
+ "isPermanent",
+ "deviceId",
+ "state",
+ "life",
+ "packets",
+ "bytes",
+ "lastSeen"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "12103425214920339"
+ },
+ "tableId": {
+ "type": "integer",
+ "format": "int32",
+ "example": 3
+ },
+ "appId": {
+ "type": "string",
+ "example": "org.onosproject.core"
+ },
+ "groupId": {
+ "type": "integer",
+ "format": "int64",
+ "example": 0
+ },
+ "priority": {
+ "type": "integer",
+ "format": "int32",
+ "example": 40000
+ },
+ "timeout": {
+ "type": "integer",
+ "format": "int32",
+ "example": 0
+ },
+ "isPermanent": {
+ "type": "boolean",
+ "example": true
+ },
+ "deviceId": {
+ "type": "string",
+ "example": "of:0000000000000003"
+ },
+ "state": {
+ "type": "string",
+ "example": "ADDED"
+ },
+ "life": {
+ "type": "integer",
+ "format": "int64",
+ "example": 69889
+ },
+ "packets": {
+ "type": "integer",
+ "format": "int64",
+ "example": 22546
+ },
+ "bytes": {
+ "type": "integer",
+ "format": "int64",
+ "example": 1826226
+ },
+ "lastSeen": {
+ "type": "integer",
+ "format": "int64",
+ "example": 1447892365670
+ },
+ "treatment": {
+ "type": "object",
+ "title": "treatment",
+ "required": [
+ "instructions",
+ "deferred"
+ ],
+ "properties": {
+ "instructions": {
+ "type": "array",
+ "title": "treatment",
+ "required": [
+ "properties",
+ "port"
+ ],
+ "items": {
+ "type": "object",
+ "title": "instruction",
+ "required": [
+ "type",
+ "port"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "example": "OUTPUT"
+ },
+ "port": {
+ "type": "string",
+ "example": "CONTROLLER"
+ }
+ }
+ }
+ },
+ "deferred": {
+ "type": "array",
+ "xml": {
+ "name": "deferred",
+ "wrapped": true
+ },
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "selector": {
+ "type": "object",
+ "title": "selector",
+ "required": [
+ "criteria"
+ ],
+ "properties": {
+ "criteria": {
+ "type": "array",
+ "xml": {
+ "name": "criteria",
+ "wrapped": true
+ },
+ "items": {
+ "type": "object",
+ "title": "criteria",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Ethernet field name",
+ "example": "ETH_TYPE"
+ },
+ "ethType": {
+ "type": "int64",
+ "format": "int64",
+ "example": "0x88cc",
+ "description": "Ethernet frame type"
+ },
+ "mac": {
+ "type": "string",
+ "example": "00:00:11:00:00:01"
+ },
+ "port": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "Match port"
+ },
+ "metadata": {
+ "type": "Hex16",
+ "format": "Hex16",
+ "example": "0xabcdL",
+ "description": "Metadata passed between tables"
+ },
+ "vlanId": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": "0x1000"
+ },
+ "priority": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "VLAN priority."
+ },
+ "ipDscp": {
+ "type": "byte",
+ "format": "byte",
+ "description": "IP DSCP (6 bits in ToS field)"
+ },
+ "ipEcn": {
+ "type": "byte",
+ "format": "byte",
+ "description": "IP ECN (2 bits in ToS field)."
+ },
+ "protocol": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "IP protocol"
+ },
+ "ip": {
+ "type": "string",
+ "example": "10.1.1.0/24",
+ "description": "IP source address"
+ },
+ "tcpPort": {
+ "type": "integer",
+ "format": "uint16",
+ "example": 1,
+ "description": "TCP source address"
+ },
+ "udpPort": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "UDP source address"
+ },
+ "sctpPort": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "SCTP source address"
+ },
+ "icmpType": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV4 code (RFC0792)"
+ },
+ "icmpCode": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV4 code (RFC0792)"
+ },
+ "flowLabel": {
+ "type": "Hex16",
+ "format": "Hex16",
+ "example": "0xffffe",
+ "description": "IPv6 Flow Label (RFC 6437)"
+ },
+ "icmpv6Type": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV6 type (RFC2463)"
+ },
+ "icmpv6Code": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV6 code (RFC2463)"
+ },
+ "targetAddress": {
+ "type": "String",
+ "example": "10.1.1.0/24",
+ "description": "IPv6 Neighbor discovery target address"
+ },
+ "label": {
+ "type": "int32",
+ "format": "int32",
+ "example": 1,
+ "description": "MPLS label"
+ },
+ "exthdrFlags": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "IPv6 extension header pseudo-field"
+ },
+ "lambda": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "wavelength abstraction"
+ },
+ "gridType": {
+ "type": "String",
+ "example": "DWDM",
+ "description": "Type of wavelength grid"
+ },
+ "channelSpacing": {
+ "type": "int64",
+ "format": "int64",
+ "example": 100,
+ "description": "Optical channel spacing"
+ },
+ "spacingMultiplier": {
+ "type": "integer",
+ "format": "int64",
+ "example": 4,
+ "description": "Optical channel spacing multiplier"
+ },
+ "slotGranularity": {
+ "type": "int64",
+ "format": "int64",
+ "example": 8
+ },
+ "ochSignalId": {
+ "type": "integer",
+ "format": "int64",
+ "example": 1,
+ "description": "Optical channel signal ID"
+ },
+ "tunnelId": {
+ "type": "int64",
+ "format": "int64",
+ "example": 5,
+ "description": "Tunnel ID"
+ },
+ "ochSignalType": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "Optical channel signal type"
+ },
+ "oduSignalId": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "ODU (Optical channel Data Unit) signal ID."
+ },
+ "tributaryPortNumber": {
+ "type": "int64",
+ "format": "int64",
+ "example": 11,
+ "description": "OPU (Optical channel Payload Unit) port number."
+ },
+ "tributarySlotLen": {
+ "type": "int64",
+ "format": "int64",
+ "example": 80,
+ "description": "OPU (Optical channel Payload Unit) slot length."
+ },
+ "tributarySlotBitmap": {
+ "type": "array",
+ "title": "tributarySlotBitmap",
+ "description": "OPU (Optical channel Payload Unit) slot bitmap.",
+ "required": [
+ "byte",
+ "port"
+ ],
+ "items": {
+ "type": "byte",
+ "title": "byte",
+ "example": 1
+ }
+ },
+ "oduSignalType": {
+ "type": "int64",
+ "format": "int64",
+ "example": 4,
+ "description": "ODU (Optical channel Data Unit) signal type."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/web/api/src/main/resources/definitions/FlowRules.json b/web/api/src/main/resources/definitions/FlowRules.json
new file mode 100644
index 0000000..026b726
--- /dev/null
+++ b/web/api/src/main/resources/definitions/FlowRules.json
@@ -0,0 +1,344 @@
+{
+ "type": "object",
+ "title": "flows",
+ "required": [
+ "flows"
+ ],
+ "properties": {
+ "flows": {
+ "type": "array",
+ "xml": {
+ "name": "flows",
+ "wrapped": true
+ },
+ "items": {
+ "type": "object",
+ "title": "flow",
+ "required": [
+ "id",
+ "tableId",
+ "appId",
+ "groupId",
+ "priority",
+ "timeout",
+ "isPermanent",
+ "deviceId"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "12103425214920339"
+ },
+ "tableId": {
+ "type": "integer",
+ "format": "int32",
+ "example": 3
+ },
+ "appId": {
+ "type": "string",
+ "example": "org.onosproject.core"
+ },
+ "groupId": {
+ "type": "integer",
+ "format": "int64",
+ "example": 0
+ },
+ "priority": {
+ "type": "integer",
+ "format": "int32",
+ "example": 40000
+ },
+ "timeout": {
+ "type": "integer",
+ "format": "int32",
+ "example": 0
+ },
+ "isPermanent": {
+ "type": "boolean",
+ "example": true
+ },
+ "deviceId": {
+ "type": "string",
+ "example": "of:0000000000000003"
+ },
+ "treatment": {
+ "type": "object",
+ "title": "treatment",
+ "required": [
+ "instructions",
+ "deferred"
+ ],
+ "properties": {
+ "instructions": {
+ "type": "array",
+ "title": "treatment",
+ "required": [
+ "properties",
+ "port"
+ ],
+ "items": {
+ "type": "object",
+ "title": "instruction",
+ "required": [
+ "type",
+ "port"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "example": "OUTPUT"
+ },
+ "port": {
+ "type": "string",
+ "example": "CONTROLLER"
+ }
+ }
+ }
+ },
+ "deferred": {
+ "type": "array",
+ "xml": {
+ "name": "deferred",
+ "wrapped": true
+ },
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "selector": {
+ "type": "object",
+ "title": "selector",
+ "required": [
+ "criteria"
+ ],
+ "properties": {
+ "criteria": {
+ "type": "array",
+ "xml": {
+ "name": "criteria",
+ "wrapped": true
+ },
+ "items": {
+ "type": "object",
+ "title": "criteria",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Ethernet field name",
+ "example": "ETH_TYPE"
+ },
+ "ethType": {
+ "type": "int64",
+ "format": "int64",
+ "example": "0x88cc",
+ "description": "Ethernet frame type"
+ },
+ "mac": {
+ "type": "string",
+ "example": "00:00:11:00:00:01"
+ },
+ "port": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "Match port"
+ },
+ "metadata": {
+ "type": "Hex16",
+ "format": "Hex16",
+ "example": "0xabcdL",
+ "description": "Metadata passed between tables"
+ },
+ "vlanId": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": "0x1000"
+ },
+ "priority": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "VLAN priority."
+ },
+ "ipDscp": {
+ "type": "byte",
+ "format": "byte",
+ "description": "IP DSCP (6 bits in ToS field)"
+ },
+ "ipEcn": {
+ "type": "byte",
+ "format": "byte",
+ "description": "IP ECN (2 bits in ToS field)."
+ },
+ "protocol": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "IP protocol"
+ },
+ "ip": {
+ "type": "string",
+ "example": "10.1.1.0/24",
+ "description": "IP source address"
+ },
+ "tcpPort": {
+ "type": "integer",
+ "format": "uint16",
+ "example": 1,
+ "description": "TCP source address"
+ },
+ "udpPort": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "UDP source address"
+ },
+ "sctpPort": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "SCTP source address"
+ },
+ "icmpType": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV4 code (RFC0792)"
+ },
+ "icmpCode": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV4 code (RFC0792)"
+ },
+ "flowLabel": {
+ "type": "Hex16",
+ "format": "Hex16",
+ "example": "0xffffe",
+ "description": "IPv6 Flow Label (RFC 6437)"
+ },
+ "icmpv6Type": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV6 type (RFC2463)"
+ },
+ "icmpv6Code": {
+ "type": "uint16",
+ "format": "uint16",
+ "example": 1,
+ "description": "Internet Control Message Protocol for IPV6 code (RFC2463)"
+ },
+ "targetAddress": {
+ "type": "String",
+ "example": "10.1.1.0/24",
+ "description": "IPv6 Neighbor discovery target address"
+ },
+ "label": {
+ "type": "int32",
+ "format": "int32",
+ "example": 1,
+ "description": "MPLS label"
+ },
+ "exthdrFlags": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "IPv6 extension header pseudo-field"
+ },
+ "lambda": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "wavelength abstraction"
+ },
+ "gridType": {
+ "type": "String",
+ "example": "DWDM",
+ "description": "Type of wavelength grid"
+ },
+ "channelSpacing": {
+ "type": "int64",
+ "format": "int64",
+ "example": 100,
+ "description": "Optical channel spacing"
+ },
+ "spacingMultiplier": {
+ "type": "integer",
+ "format": "int64",
+ "example": 4,
+ "description": "Optical channel spacing multiplier"
+ },
+ "slotGranularity": {
+ "type": "int64",
+ "format": "int64",
+ "example": 8
+ },
+ "ochSignalId": {
+ "type": "integer",
+ "format": "int64",
+ "example": 1,
+ "description": "Optical channel signal ID"
+ },
+ "tunnelId": {
+ "type": "int64",
+ "format": "int64",
+ "example": 5,
+ "description": "Tunnel ID"
+ },
+ "ochSignalType": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "Optical channel signal type"
+ },
+ "oduSignalId": {
+ "type": "int64",
+ "format": "int64",
+ "example": 1,
+ "description": "ODU (Optical channel Data Unit) signal ID."
+ },
+ "tributaryPortNumber": {
+ "type": "int64",
+ "format": "int64",
+ "example": 11,
+ "description": "OPU (Optical channel Payload Unit) port number."
+ },
+ "tributarySlotLen": {
+ "type": "int64",
+ "format": "int64",
+ "example": 80,
+ "description": "OPU (Optical channel Payload Unit) slot length."
+ },
+ "tributarySlotBitmap": {
+ "type": "array",
+ "title": "tributarySlotBitmap",
+ "description": "OPU (Optical channel Payload Unit) slot bitmap.",
+ "required": [
+ "byte",
+ "port"
+ ],
+ "items": {
+ "type": "byte",
+ "title": "byte",
+ "example": 1
+ }
+ },
+ "oduSignalType": {
+ "type": "int64",
+ "format": "int64",
+ "example": 4,
+ "description": "ODU (Optical channel Data Unit) signal type."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/web/api/src/main/resources/definitions/Flows.json b/web/api/src/main/resources/definitions/Flows.json
deleted file mode 100644
index cb8699a..0000000
--- a/web/api/src/main/resources/definitions/Flows.json
+++ /dev/null
@@ -1,373 +0,0 @@
-{
- "type": "object",
- "title": "flows",
- "required": [
- "flows"
- ],
- "properties": {
- "flows": {
- "type": "array",
- "xml": {
- "name": "flows",
- "wrapped": true
- },
- "items": {
- "type": "object",
- "title": "flow",
- "required": [
- "id",
- "tableId",
- "appId",
- "groupId",
- "priority",
- "timeout",
- "isPermanent",
- "deviceId",
- "state",
- "life",
- "packets",
- "bytes",
- "lastSeen"
- ],
- "properties": {
- "id": {
- "type": "string",
- "example": "12103425214920339"
- },
- "tableId": {
- "type": "integer",
- "format": "int32",
- "example": 3
- },
- "appId": {
- "type": "string",
- "example": "org.onosproject.core"
- },
- "groupId": {
- "type": "integer",
- "format": "int64",
- "example": 0
- },
- "priority": {
- "type": "integer",
- "format": "int32",
- "example": 40000
- },
- "timeout": {
- "type": "integer",
- "format": "int32",
- "example": 0
- },
- "isPermanent": {
- "type": "boolean",
- "example": true
- },
- "deviceId": {
- "type": "string",
- "example": "of:0000000000000003"
- },
- "state": {
- "type": "string",
- "example": "ADDED"
- },
- "life": {
- "type": "integer",
- "format": "int64",
- "example": 69889
- },
- "packets": {
- "type": "integer",
- "format": "int64",
- "example": 22546
- },
- "bytes": {
- "type": "integer",
- "format": "int64",
- "example": 1826226
- },
- "lastSeen": {
- "type": "integer",
- "format": "int64",
- "example": 1447892365670
- },
- "treatment": {
- "type": "object",
- "title": "treatment",
- "required": [
- "instructions",
- "deferred"
- ],
- "properties": {
- "instructions": {
- "type": "array",
- "title": "treatment",
- "required": [
- "properties",
- "port"
- ],
- "items": {
- "type": "object",
- "title": "instruction",
- "required": [
- "type",
- "port"
- ],
- "properties": {
- "type": {
- "type": "string",
- "example": "OUTPUT"
- },
- "port": {
- "type": "string",
- "example": "CONTROLLER"
- }
- }
- }
- },
- "deferred": {
- "type": "array",
- "xml": {
- "name": "deferred",
- "wrapped": true
- },
- "items": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "selector": {
- "type": "object",
- "title": "selector",
- "required": [
- "criteria"
- ],
- "properties": {
- "criteria": {
- "type": "array",
- "xml": {
- "name": "criteria",
- "wrapped": true
- },
- "items": {
- "type": "object",
- "title": "criteria",
- "properties": {
- "type": {
- "type": "string",
- "description":"Ethernet field name",
- "example": "ETH_TYPE"
- },
- "ethType": {
- "type": "int64",
- "format": "int64",
- "example": "0x88cc",
- "description":"Ethernet frame type"
- },
- "mac": {
- "type": "string",
- "example": "00:00:11:00:00:01"
- },
- "port": {
- "type": "int64",
- "format": "int64",
- "example": 1,
- "description":"Match port"
- },
- "metadata": {
- "type": "Hex16",
- "format": "Hex16",
- "example": "0xabcdL",
- "description":"Metadata passed between tables"
- },
- "vlanId": {
- "type": "uint16",
- "format": "uint16",
- "example": "0x1000"
- },
- "priority": {
- "type": "int64",
- "format": "int64",
- "example": 1,
- "description":"VLAN priority."
- },
- "ipDscp": {
- "type": "byte",
- "format": "byte",
- "description":"IP DSCP (6 bits in ToS field)"
- },
- "ipEcn": {
- "type": "byte",
- "format": "byte",
- "description":"IP ECN (2 bits in ToS field)."
- },
- "protocol": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"IP protocol"
- },
- "ip": {
- "type": "string",
- "example": "10.1.1.0/24",
- "description":"IP source address"
- },
- "tcpPort": {
- "type": "integer",
- "format": "uint16",
- "example": 1,
- "description":"TCP source address"
- },
- "udpPort": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"UDP source address"
- },
- "sctpPort": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"SCTP source address"
- },
- "icmpType": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"Internet Control Message Protocol for IPV4 code (RFC0792)"
- },
- "icmpCode": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"Internet Control Message Protocol for IPV4 code (RFC0792)"
- },
- "flowLabel": {
- "type": "Hex16",
- "format": "Hex16",
- "example": "0xffffe",
- "description":"IPv6 Flow Label (RFC 6437)"
- },
- "icmpv6Type": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"Internet Control Message Protocol for IPV6 type (RFC2463)"
- },
- "icmpv6Code": {
- "type": "uint16",
- "format": "uint16",
- "example": 1,
- "description":"Internet Control Message Protocol for IPV6 code (RFC2463)"
- },
- "targetAddress": {
- "type": "String",
- "example": "10.1.1.0/24",
- "description":"IPv6 Neighbor discovery target address"
- },
- "label": {
- "type": "int32",
- "format": "int32",
- "example": 1,
- "description":"MPLS label"
- },
- "exthdrFlags": {
- "type": "int64",
- "format": "int64",
- "example": 1,
- "description":"IPv6 extension header pseudo-field"
- },
- "lambda": {
- "type": "int64",
- "format": "int64",
- "example": 1,
- "description":"wavelength abstraction"
- },
- "gridType": {
- "type": "String",
- "example": "DWDM",
- "description":"Type of wavelength grid"
- },
- "channelSpacing": {
- "type": "int64",
- "format": "int64",
- "example": 100,
- "description":"Optical channel spacing"
- },
- "spacingMultiplier": {
- "type": "integer",
- "format": "int64",
- "example": 4,
- "description":"Optical channel spacing multiplier"
- },
- "slotGranularity": {
- "type": "int64",
- "format": "int64",
- "example": 8
- },
- "ochSignalId": {
- "type": "integer",
- "format": "int64",
- "example": 1,
- "description":"Optical channel signal ID"
- },
- "tunnelId": {
- "type": "int64",
- "format": "int64",
- "example": 5,
- "description":"Tunnel ID"
- },
- "ochSignalType": {
- "type": "int64",
- "format": "int64",
- "example": 1,
- "description":"Optical channel signal type"
- },
- "oduSignalId": {
- "type": "int64",
- "format": "int64",
- "example": 1,
- "description":"ODU (Optical channel Data Unit) signal ID."
- },
- "tributaryPortNumber": {
- "type": "int64",
- "format": "int64",
- "example": 11,
- "description":"OPU (Optical channel Payload Unit) port number."
- },
- "tributarySlotLen": {
- "type": "int64",
- "format": "int64",
- "example": 80,
- "description":"OPU (Optical channel Payload Unit) slot length."
- },
- "tributarySlotBitmap": {
- "type": "array",
- "title": "tributarySlotBitmap",
- "description":"OPU (Optical channel Payload Unit) slot bitmap.",
- "required": [
- "byte",
- "port"
- ],
- "items": {
- "type": "byte",
- "title": "byte",
- "example": 1
- }
- },
- "oduSignalType": {
- "type": "int64",
- "format": "int64",
- "example": 4,
- "description":"ODU (Optical channel Data Unit) signal type."
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java
index 4aede15..4687e44 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/FlowsResourceTest.java
@@ -19,6 +19,7 @@
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import org.hamcrest.Description;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;
@@ -29,6 +30,7 @@
import org.onlab.osgi.TestServiceDirectory;
import org.onlab.packet.MacAddress;
import org.onlab.rest.BaseResource;
+import org.onosproject.app.ApplicationService;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.codec.impl.FlowRuleCodec;
@@ -65,6 +67,7 @@
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyShort;
+import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
@@ -98,6 +101,8 @@
final Device device2 = new DefaultDevice(null, deviceId2, Device.Type.OTHER,
"", "", "", "", null);
+ final ApplicationService mockApplicationService = createMock(ApplicationService.class);
+
final MockFlowEntry flow1 = new MockFlowEntry(deviceId1, 1);
final MockFlowEntry flow2 = new MockFlowEntry(deviceId1, 2);
@@ -107,6 +112,14 @@
final MockFlowEntry flow5 = new MockFlowEntry(deviceId2, 5);
final MockFlowEntry flow6 = new MockFlowEntry(deviceId2, 6);
+ final MockFlowRule flowRule1 = new MockFlowRule(deviceId1, 1);
+ final MockFlowRule flowRule2 = new MockFlowRule(deviceId1, 2);
+
+ final MockFlowRule flowRule3 = new MockFlowRule(deviceId2, 3);
+ final MockFlowRule flowRule4 = new MockFlowRule(deviceId2, 4);
+
+ final Set<FlowRule> flowRules = Sets.newHashSet();
+
/**
* Mock class for a flow entry.
*/
@@ -219,6 +232,83 @@
}
/**
+ * Mock class for a flow rule.
+ */
+ private static class MockFlowRule implements FlowRule {
+
+ final DeviceId deviceId;
+ final long baseValue;
+ TrafficTreatment treatment;
+ TrafficSelector selector;
+
+ public MockFlowRule(DeviceId deviceId, long id) {
+ this.deviceId = deviceId;
+ this.baseValue = id * 100;
+ }
+
+ @Override
+ public FlowId id() {
+ final long id = baseValue + 55;
+ return FlowId.valueOf(id);
+ }
+
+ @Override
+ public short appId() {
+ return 4;
+ }
+
+ @Override
+ public GroupId groupId() {
+ return new DefaultGroupId(3);
+ }
+
+ @Override
+ public int priority() {
+ return 0;
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public TrafficSelector selector() {
+ return selector;
+ }
+
+ @Override
+ public TrafficTreatment treatment() {
+ return treatment;
+ }
+
+ @Override
+ public int timeout() {
+ return (int) (baseValue + 77);
+ }
+
+ @Override
+ public boolean isPermanent() {
+ return false;
+ }
+
+ @Override
+ public int tableId() {
+ return 0;
+ }
+
+ @Override
+ public boolean exactMatch(FlowRule rule) {
+ return false;
+ }
+
+ @Override
+ public FlowRuleExtPayLoad payLoad() {
+ return null;
+ }
+ }
+
+ /**
* Populates some flows used as testing data.
*/
private void setupMockFlows() {
@@ -249,6 +339,26 @@
}
/**
+ * Populates some flow rules used as testing data.
+ */
+ private void setupMockFlowRules() {
+ flowRule2.treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(MacAddress.BROADCAST)
+ .build();
+ flowRule2.selector = DefaultTrafficSelector.builder()
+ .matchEthType((short) 3)
+ .matchIPProtocol((byte) 9)
+ .build();
+ flowRule4.treatment = DefaultTrafficTreatment.builder()
+ .build();
+
+ flowRules.add(flowRule1);
+ flowRules.add(flowRule2);
+ flowRules.add(flowRule3);
+ flowRules.add(flowRule4);
+ }
+
+ /**
* Sets up the global values for all the tests.
*/
@Before
@@ -264,6 +374,8 @@
// Mock Core Service
expect(mockCoreService.getAppId(anyShort()))
.andReturn(NetTestTools.APP_ID).anyTimes();
+ expect(mockCoreService.getAppId(anyString()))
+ .andReturn(NetTestTools.APP_ID).anyTimes();
expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
.andReturn(APP_ID).anyTimes();
replay(mockCoreService);
@@ -276,7 +388,8 @@
.add(FlowRuleService.class, mockFlowService)
.add(DeviceService.class, mockDeviceService)
.add(CodecService.class, codecService)
- .add(CoreService.class, mockCoreService);
+ .add(CoreService.class, mockCoreService)
+ .add(ApplicationService.class, mockApplicationService);
BaseResource.setServiceDirectory(testDirectory);
}
@@ -294,12 +407,12 @@
* Hamcrest matcher to check that a flow representation in JSON matches
* the actual flow entry.
*/
- public static class FlowJsonMatcher extends TypeSafeMatcher<JsonObject> {
+ public static class FlowEntryJsonMatcher extends TypeSafeMatcher<JsonObject> {
private final FlowEntry flow;
private final String expectedAppId;
private String reason = "";
- public FlowJsonMatcher(FlowEntry flowValue, String expectedAppIdValue) {
+ public FlowEntryJsonMatcher(FlowEntry flowValue, String expectedAppIdValue) {
flow = flowValue;
expectedAppId = expectedAppIdValue;
}
@@ -398,19 +511,19 @@
* @param flow flow object we are looking for
* @return matcher
*/
- private static FlowJsonMatcher matchesFlow(FlowEntry flow, String expectedAppName) {
- return new FlowJsonMatcher(flow, expectedAppName);
+ private static FlowEntryJsonMatcher matchesFlow(FlowEntry flow, String expectedAppName) {
+ return new FlowEntryJsonMatcher(flow, expectedAppName);
}
/**
* Hamcrest matcher to check that a flow is represented properly in a JSON
* array of flows.
*/
- public static class FlowJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+ public static class FlowEntryJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
private final FlowEntry flow;
private String reason = "";
- public FlowJsonArrayMatcher(FlowEntry flowValue) {
+ public FlowEntryJsonArrayMatcher(FlowEntry flowValue) {
flow = flowValue;
}
@@ -452,8 +565,174 @@
* @param flow flow object we are looking for
* @return matcher
*/
- private static FlowJsonArrayMatcher hasFlow(FlowEntry flow) {
- return new FlowJsonArrayMatcher(flow);
+ private static FlowEntryJsonArrayMatcher hasFlow(FlowEntry flow) {
+ return new FlowEntryJsonArrayMatcher(flow);
+ }
+
+ /**
+ * Hamcrest matcher to check that a flow representation in JSON matches
+ * the actual flow rule.
+ */
+ public static class FlowRuleJsonMatcher extends TypeSafeMatcher<JsonObject> {
+ private final FlowRule flow;
+ private final String expectedAppId;
+ private String reason = "";
+
+ public FlowRuleJsonMatcher(FlowRule flowValue, String expectedAppIdValue) {
+ flow = flowValue;
+ expectedAppId = expectedAppIdValue;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonObject jsonFlow) {
+ // check id
+ final String jsonId = jsonFlow.get("id").asString();
+ final String flowId = Long.toString(flow.id().value());
+ if (!jsonId.equals(flowId)) {
+ reason = "id " + flow.id().toString();
+ return false;
+ }
+
+ // check application id
+ final String jsonAppId = jsonFlow.get("appId").asString();
+ if (!jsonAppId.equals(expectedAppId)) {
+ reason = "appId " + Short.toString(flow.appId());
+ return false;
+ }
+
+ // check device id
+ final String jsonDeviceId = jsonFlow.get("deviceId").asString();
+ if (!jsonDeviceId.equals(flow.deviceId().toString())) {
+ reason = "deviceId " + flow.deviceId();
+ return false;
+ }
+
+ // check treatment and instructions array
+ if (flow.treatment() != null) {
+ final JsonObject jsonTreatment = jsonFlow.get("treatment").asObject();
+ final JsonArray jsonInstructions = jsonTreatment.get("instructions").asArray();
+ if (flow.treatment().immediate().size() != jsonInstructions.size()) {
+ reason = "instructions array size of " +
+ Integer.toString(flow.treatment().immediate().size());
+ return false;
+ }
+ for (final Instruction instruction : flow.treatment().immediate()) {
+ boolean instructionFound = false;
+ for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) {
+ final String jsonType =
+ jsonInstructions.get(instructionIndex)
+ .asObject().get("type").asString();
+ final String instructionType = instruction.type().name();
+ if (jsonType.equals(instructionType)) {
+ instructionFound = true;
+ }
+ }
+ if (!instructionFound) {
+ reason = "instruction " + instruction.toString();
+ return false;
+ }
+ }
+ }
+
+ // check selector and criteria array
+ if (flow.selector() != null) {
+ final JsonObject jsonTreatment = jsonFlow.get("selector").asObject();
+ final JsonArray jsonCriteria = jsonTreatment.get("criteria").asArray();
+ if (flow.selector().criteria().size() != jsonCriteria.size()) {
+ reason = "criteria array size of " +
+ Integer.toString(flow.selector().criteria().size());
+ return false;
+ }
+ for (final Criterion criterion : flow.selector().criteria()) {
+ boolean criterionFound = false;
+
+ for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) {
+ final String jsonType =
+ jsonCriteria.get(criterionIndex)
+ .asObject().get("type").asString();
+ final String criterionType = criterion.type().name();
+ if (jsonType.equals(criterionType)) {
+ criterionFound = true;
+ }
+ }
+ if (!criterionFound) {
+ reason = "criterion " + criterion.toString();
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(reason);
+ }
+ }
+
+ /**
+ * Factory to allocate a flow matcher.
+ *
+ * @param flow flow rule object we are looking for
+ * @return matcher
+ */
+ private static FlowRuleJsonMatcher matchesFlowRule(FlowRule flow, String expectedAppName) {
+ return new FlowRuleJsonMatcher(flow, expectedAppName);
+ }
+
+ /**
+ * Hamcrest matcher to check that a flow is represented properly in a JSON
+ * array of flow rules.
+ */
+ public static class FlowRuleJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
+ private final FlowRule flow;
+ private String reason = "";
+
+ public FlowRuleJsonArrayMatcher(FlowRule flowValue) {
+ flow = flowValue;
+ }
+
+ @Override
+ public boolean matchesSafely(JsonArray json) {
+ boolean flowFound = false;
+
+ for (int jsonFlowIndex = 0; jsonFlowIndex < json.size();
+ jsonFlowIndex++) {
+
+ final JsonObject jsonFlow = json.get(jsonFlowIndex).asObject();
+
+ final String flowId = Long.toString(flow.id().value());
+ final String jsonFlowId = jsonFlow.get("id").asString();
+ if (jsonFlowId.equals(flowId)) {
+ flowFound = true;
+
+ // We found the correct flow, check attribute values
+ assertThat(jsonFlow, matchesFlowRule(flow, APP_ID.name()));
+ }
+ }
+ if (!flowFound) {
+ reason = "Flow with id " + flow.id().toString() + " not found";
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(reason);
+ }
+ }
+
+ /**
+ * Factory to allocate a flow array matcher.
+ *
+ * @param flow flow rule object we are looking for
+ * @return matcher
+ */
+ private static FlowRuleJsonArrayMatcher hasFlowRule(FlowRule flow) {
+ return new FlowRuleJsonArrayMatcher(flow);
}
/**
@@ -572,7 +851,7 @@
* Tests creating a flow with POST.
*/
@Test
- public void testPost() {
+ public void testPostWithoutAppId() {
mockFlowService.applyFlowRules(anyObject());
expectLastCall();
replay(mockFlowService);
@@ -590,6 +869,28 @@
}
/**
+ * Tests creating a flow with POST while specifying application identifier.
+ */
+ @Test
+ public void testPostWithAppId() {
+ mockFlowService.applyFlowRules(anyObject());
+ expectLastCall();
+ replay(mockFlowService);
+
+ WebTarget wt = target();
+ InputStream jsonStream = FlowsResourceTest.class
+ .getResourceAsStream("post-flow.json");
+
+ Response response = wt.path("flows/of:0000000000000001")
+ .queryParam("appId", "org.onosproject.rest")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
+ String location = response.getLocation().getPath();
+ assertThat(location, Matchers.startsWith("/flows/of:0000000000000001/"));
+ }
+
+ /**
* Tests deleting a flow.
*/
@Test
@@ -609,4 +910,55 @@
assertThat(deleteResponse.getStatus(),
is(HttpURLConnection.HTTP_NO_CONTENT));
}
+
+ /**
+ * Tests the result of a rest api GET for an application.
+ */
+ @Test
+ public void testGetFlowByAppId() {
+ setupMockFlowRules();
+
+ expect(mockApplicationService.getId(anyObject())).andReturn(APP_ID).anyTimes();
+ replay(mockApplicationService);
+
+ expect(mockFlowService.getFlowRulesById(APP_ID)).andReturn(flowRules).anyTimes();
+ replay(mockFlowService);
+
+ final WebTarget wt = target();
+ final String response = wt.path("flows/application/1").request().get(String.class);
+ final JsonObject result = Json.parse(response).asObject();
+ assertThat(result, notNullValue());
+
+ assertThat(result.names(), hasSize(1));
+ assertThat(result.names().get(0), is("flows"));
+ final JsonArray jsonFlows = result.get("flows").asArray();
+ assertThat(jsonFlows, notNullValue());
+ assertThat(jsonFlows, hasFlowRule(flowRule1));
+ assertThat(jsonFlows, hasFlowRule(flowRule2));
+ assertThat(jsonFlows, hasFlowRule(flowRule3));
+ assertThat(jsonFlows, hasFlowRule(flowRule4));
+ }
+
+ /**
+ * Tests the result of a rest api DELETE for an application.
+ */
+ @Test
+ public void testRemoveFlowByAppId() {
+ expect(mockApplicationService.getId(anyObject())).andReturn(APP_ID).anyTimes();
+ replay(mockApplicationService);
+
+ mockFlowService.removeFlowRulesById(APP_ID);
+ expectLastCall();
+ replay(mockFlowService);
+
+ WebTarget wt = target();
+
+ String location = "/flows/application/1";
+
+ Response deleteResponse = wt.path(location)
+ .request()
+ .delete();
+ assertThat(deleteResponse.getStatus(),
+ is(HttpURLConnection.HTTP_NO_CONTENT));
+ }
}