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 e36fe89f6f..1a41e5c391 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.JsonNode; 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.FlowRule; 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 @@ public final class FlowRuleCodec extends JsonCodec { 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) ? "" : 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 treatmentCodec = + context.codec(TrafficTreatment.class); + result.set("treatment", treatmentCodec.encode(flowRule.treatment(), context)); + } + + if (flowRule.selector() != null) { + final JsonCodec 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 @@ public final class FlowRuleCodec extends JsonCodec { 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 9c5e3fda44..11b7eb66e2 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 @@ package org.onosproject.codec.impl; 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.onlab.packet.VlanId; 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.io.InputStream; 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 @@ public class FlowRuleCodecTest { 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); } @@ -165,6 +171,118 @@ public class FlowRuleCodecTest { SortedMap 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 { + + 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. * 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 fc99c49fec..42e9590b7a 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.fasterxml.jackson.databind.node.ObjectNode; 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.POST; 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 @@ public class FlowsWebResource extends AbstractWebResource { 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 @@ public class FlowsWebResource extends AbstractWebResource { * 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 @@ public class FlowsWebResource extends AbstractWebResource { @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 rules = codec(FlowRule.class).decode(flowsArray, this); service.applyFlowRules(rules.toArray(new FlowRule[rules.size()])); rules.forEach(flowRule -> { @@ -131,10 +140,11 @@ public class FlowsWebResource extends AbstractWebResource { * * @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 flowEntries = @@ -150,13 +160,13 @@ public class FlowsWebResource extends AbstractWebResource { } /** - * 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) @@ -177,6 +187,43 @@ public class FlowsWebResource extends AbstractWebResource { return ok(root).build(); } + /** + * 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 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.
@@ -187,6 +234,7 @@ public class FlowsWebResource extends AbstractWebResource { * 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 @@ public class FlowsWebResource extends AbstractWebResource { @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 @@ public class FlowsWebResource extends AbstractWebResource { "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 @@ public class FlowsWebResource extends AbstractWebResource { } /** - * 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 0000000000..15f86285c6 --- /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 0000000000..026b7267c3 --- /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 cb8699ad7d..0000000000 --- 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 4aede1583b..4687e449a8 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.Json; 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.ServiceDirectory; 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 java.util.Set; 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 @@ public class FlowsResourceTest extends ResourceTest { 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 @@ public class FlowsResourceTest extends ResourceTest { 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 flowRules = Sets.newHashSet(); + /** * Mock class for a flow entry. */ @@ -218,6 +231,83 @@ public class FlowsResourceTest extends ResourceTest { } } + /** + * 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. */ @@ -248,6 +338,26 @@ public class FlowsResourceTest extends ResourceTest { .andReturn(rules.get(deviceId2)).anyTimes(); } + /** + * 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. */ @@ -264,6 +374,8 @@ public class FlowsResourceTest extends ResourceTest { // 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 @@ public class FlowsResourceTest extends ResourceTest { .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 @@ public class FlowsResourceTest extends ResourceTest { * Hamcrest matcher to check that a flow representation in JSON matches * the actual flow entry. */ - public static class FlowJsonMatcher extends TypeSafeMatcher { + public static class FlowEntryJsonMatcher extends TypeSafeMatcher { 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 @@ public class FlowsResourceTest extends ResourceTest { * @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 { + public static class FlowEntryJsonArrayMatcher extends TypeSafeMatcher { private final FlowEntry flow; private String reason = ""; - public FlowJsonArrayMatcher(FlowEntry flowValue) { + public FlowEntryJsonArrayMatcher(FlowEntry flowValue) { flow = flowValue; } @@ -452,8 +565,174 @@ public class FlowsResourceTest extends ResourceTest { * @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 { + 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 { + 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 @@ public class FlowsResourceTest extends ResourceTest { * Tests creating a flow with POST. */ @Test - public void testPost() { + public void testPostWithoutAppId() { mockFlowService.applyFlowRules(anyObject()); expectLastCall(); replay(mockFlowService); @@ -589,6 +868,28 @@ public class FlowsResourceTest extends ResourceTest { assertThat(location, Matchers.startsWith("/flows/of:0000000000000001/")); } + /** + * 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. */ @@ -609,4 +910,55 @@ public class FlowsResourceTest extends ResourceTest { 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)); + } }