ONOS-2144 - Complete implementation of REST API for flows
- URL for creation of a single flow is now /flows/{deviceId}
- On successful creation of a flow, Location header contains
a reference to the new object's URI
- POST operation returns status code of CREATED
- implement DELETE operation for /flows/{deviceId}/{flowId}
- removed deprecations and warnings from REST API unit
test for flows.
Change-Id: Idb43a651a659e60c07a6f36dfd69004c814b146b
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 94a1948..bd81ee0 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
@@ -33,7 +33,6 @@
*/
public final class FlowRuleCodec extends JsonCodec<FlowRule> {
- private static final String APP_ID = "appId";
private static final String PRIORITY = "priority";
private static final String TIMEOUT = "timeout";
private static final String IS_PERMANENT = "isPermanent";
@@ -42,6 +41,7 @@
private static final String SELECTOR = "selector";
private static final String MISSING_MEMBER_MESSAGE =
" member is required in FlowRule";
+ public static final String REST_APP_ID = "org.onosproject.rest";
@Override
@@ -52,10 +52,9 @@
FlowRule.Builder resultBuilder = new DefaultFlowRule.Builder();
- short appId = nullIsIllegal(json.get(APP_ID),
- APP_ID + MISSING_MEMBER_MESSAGE).shortValue();
CoreService coreService = context.getService(CoreService.class);
- resultBuilder.fromApp(coreService.getAppId(appId));
+ resultBuilder.fromApp(coreService
+ .registerApplication(REST_APP_ID));
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 5eaa20e..2e0e8b3 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
@@ -97,7 +97,7 @@
flowRuleCodec = context.codec(FlowRule.class);
assertThat(flowRuleCodec, notNullValue());
- expect(mockCoreService.getAppId(APP_ID.id()))
+ expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
.andReturn(APP_ID).anyTimes();
replay(mockCoreService);
context.registerService(CoreService.class, mockCoreService);
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json b/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json
index 6df4d0a..7061820 100644
--- a/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/criteria-flow.json
@@ -1,5 +1,4 @@
{
- "appId":-29467,
"priority":1,
"isPermanent":"false",
"timeout":1,
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json b/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
index d60362d..a57731b 100644
--- a/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
@@ -1,5 +1,4 @@
{
- "appId":-29467,
"priority":1,
"isPermanent":"false",
"timeout":1,
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json b/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json
index ba576bf..49d6b1c 100644
--- a/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/sigid-flow.json
@@ -1,5 +1,4 @@
{
- "appId":-29467,
"priority":1,
"isPermanent":"false",
"timeout":1,
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json b/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json
index 4011c2d..dc241f5 100644
--- a/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/simple-flow.json
@@ -1,5 +1,4 @@
{
- "appId":-29467,
"priority":1,
"isPermanent":"false",
"timeout":1,
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 9c39268..260a2b9 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
@@ -17,8 +17,12 @@
import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.stream.StreamSupport;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -36,6 +40,7 @@
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.rest.AbstractWebResource;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -125,22 +130,58 @@
* Creates a flow rule from a POST of a JSON string and attempts to apply it.
*
* @param stream input JSON
- * @return status of the request - ACCEPTED if the JSON is correct,
+ * @return status of the request - CREATED if the JSON is correct,
* BAD_REQUEST if the JSON is invalid
*/
@POST
+ @Path("{deviceId}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- public Response createFlow(InputStream stream) {
+ public Response createFlow(@PathParam("deviceId") String deviceId,
+ InputStream stream) {
+ URI location;
try {
FlowRuleService service = get(FlowRuleService.class);
ObjectNode root = (ObjectNode) mapper().readTree(stream);
+ JsonNode specifiedDeviceId = root.get("deviceId");
+ if (specifiedDeviceId != null &&
+ !specifiedDeviceId.asText().equals(deviceId)) {
+ throw new IllegalArgumentException(
+ "Invalid deviceId in flow creation request");
+ }
+ root.put("deviceId", deviceId);
FlowRule rule = codec(FlowRule.class).decode(root, this);
service.applyFlowRules(rule);
- } catch (IOException ex) {
+ location = new URI(Long.toString(rule.id().value()));
+ } catch (IOException | URISyntaxException ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
- return Response.status(Response.Status.ACCEPTED).build();
+ return Response
+ .created(location)
+ .build();
+ }
+
+ /**
+ * Removes the flows for a given device with the given flow id.
+ *
+ * @param deviceId Id of device to look up
+ * @param flowId Id of flow to look up
+ */
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{deviceId}/{flowId}")
+ public void deleteFlowByDeviceIdAndFlowId(@PathParam("deviceId") String deviceId,
+ @PathParam("flowId") long flowId) {
+ final Iterable<FlowEntry> deviceEntries =
+ service.getFlowEntries(DeviceId.deviceId(deviceId));
+
+ if (!deviceEntries.iterator().hasNext()) {
+ throw new ItemNotFoundException(DEVICE_NOT_FOUND);
+ }
+
+ StreamSupport.stream(deviceEntries.spliterator(), false)
+ .filter(entry -> entry.id().value() == flowId)
+ .forEach(service::removeFlowRules);
}
}
diff --git a/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java b/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
index b18340a..63e9644 100644
--- a/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java
@@ -23,6 +23,7 @@
import javax.ws.rs.core.MediaType;
import org.hamcrest.Description;
+import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
@@ -33,12 +34,14 @@
import org.onlab.rest.BaseResource;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.codec.impl.FlowRuleCodec;
import org.onosproject.core.CoreService;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.core.GroupId;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.IndexedLambda;
import org.onosproject.net.NetTestTools;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
@@ -74,6 +77,7 @@
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
+import static org.onosproject.net.NetTestTools.APP_ID;
/**
* Unit tests for Flows REST APIs.
@@ -103,6 +107,10 @@
final MockFlowEntry flow5 = new MockFlowEntry(deviceId2, 5);
final MockFlowEntry flow6 = new MockFlowEntry(deviceId2, 6);
+ private static final String FLOW_JSON = "{\"priority\":1,\"isPermanent\":true,"
+ + "\"treatment\":{\"instructions\":[ {\"type\":\"OUTPUT\",\"port\":2}]},"
+ + "\"selector\":{\"criteria\":[ {\"type\":\"ETH_TYPE\",\"ethType\":2054}]}}";
+
/**
* Mock class for a flow entry.
*/
@@ -214,8 +222,8 @@
*/
private void setupMockFlows() {
flow2.treatment = DefaultTrafficTreatment.builder()
- .add(Instructions.modL0Lambda((short) 4))
- .add(Instructions.modL0Lambda((short) 5))
+ .add(Instructions.modL0Lambda(new IndexedLambda((short) 4)))
+ .add(Instructions.modL0Lambda(new IndexedLambda((short) 5)))
.setEthDst(MacAddress.BROADCAST)
.build();
flow2.selector = DefaultTrafficSelector.builder()
@@ -223,15 +231,15 @@
.matchIPProtocol((byte) 9)
.build();
flow4.treatment = DefaultTrafficTreatment.builder()
- .add(Instructions.modL0Lambda((short) 6))
+ .add(Instructions.modL0Lambda(new IndexedLambda((short) 6)))
.build();
final Set<FlowEntry> flows1 = new HashSet<>();
flows1.add(flow1);
flows1.add(flow2);
final Set<FlowEntry> flows2 = new HashSet<>();
- flows1.add(flow3);
- flows1.add(flow4);
+ flows2.add(flow3);
+ flows2.add(flow4);
rules.put(deviceId1, flows1);
rules.put(deviceId2, flows2);
@@ -258,6 +266,8 @@
// Mock Core Service
expect(mockCoreService.getAppId(anyShort()))
.andReturn(NetTestTools.APP_ID).anyTimes();
+ expect(mockCoreService.registerApplication(FlowRuleCodec.REST_APP_ID))
+ .andReturn(APP_ID).anyTimes();
replay(mockCoreService);
// Register the services needed for the test
@@ -565,10 +575,7 @@
*/
@Test
public void testPost() {
- String json = "{\"appId\":2,\"priority\":1,\"isPermanent\":true,"
- + "\"deviceId\":\"of:0000000000000001\","
- + "\"treatment\":{\"instructions\":[ {\"type\":\"OUTPUT\",\"port\":2}]},"
- + "\"selector\":{\"criteria\":[ {\"type\":\"ETH_TYPE\",\"ethType\":2054}]}}";
+
mockFlowService.applyFlowRules(anyObject());
expectLastCall();
@@ -577,9 +584,32 @@
WebResource rs = resource();
- ClientResponse response = rs.path("flows/")
+ ClientResponse response = rs.path("flows/of:0000000000000001")
.type(MediaType.APPLICATION_JSON_TYPE)
- .post(ClientResponse.class, json);
- assertThat(response.getStatus(), is(HttpURLConnection.HTTP_ACCEPTED));
+ .post(ClientResponse.class, FLOW_JSON);
+ assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
+ String location = response.getLocation().getPath();
+ assertThat(location, Matchers.startsWith("/flows/of:0000000000000001/"));
+ }
+
+ /**
+ * Tests deleting a flow.
+ */
+ @Test
+ public void testDelete() {
+ setupMockFlows();
+ mockFlowService.removeFlowRules(anyObject());
+ expectLastCall();
+ replay(mockFlowService);
+
+ WebResource rs = resource();
+
+ String location = "/flows/1/155";
+
+ ClientResponse deleteResponse = rs.path(location)
+ .type(MediaType.APPLICATION_JSON_TYPE)
+ .delete(ClientResponse.class);
+ assertThat(deleteResponse.getStatus(),
+ is(HttpURLConnection.HTTP_NO_CONTENT));
}
}