Jian Li
Committed by Thomas Vachuska

[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
......@@ -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,6 +38,7 @@ public final class FlowRuleCodec extends JsonCodec<FlowRule> {
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";
......@@ -44,6 +47,37 @@ public final class FlowRuleCodec extends JsonCodec<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 @@ public final class FlowRuleCodec extends JsonCodec<FlowRule> {
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();
......
......@@ -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);
}
......@@ -166,6 +172,118 @@ public class FlowRuleCodecTest {
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
......
......@@ -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<FlowRule> 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<FlowEntry> 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)
......@@ -178,6 +188,43 @@ public class FlowsWebResource extends AbstractWebResource {
}
/**
* 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 @@ 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
......
......@@ -135,9 +135,6 @@
}
}
}
}
}
}
},
"selector": {
"type": "object",
......@@ -158,14 +155,14 @@
"properties": {
"type": {
"type": "string",
"description":"Ethernet field name",
"description": "Ethernet field name",
"example": "ETH_TYPE"
},
"ethType": {
"type": "int64",
"format": "int64",
"example": "0x88cc",
"description":"Ethernet frame type"
"description": "Ethernet frame type"
},
"mac": {
"type": "string",
......@@ -175,13 +172,13 @@
"type": "int64",
"format": "int64",
"example": 1,
"description":"Match port"
"description": "Match port"
},
"metadata": {
"type": "Hex16",
"format": "Hex16",
"example": "0xabcdL",
"description":"Metadata passed between tables"
"description": "Metadata passed between tables"
},
"vlanId": {
"type": "uint16",
......@@ -192,116 +189,116 @@
"type": "int64",
"format": "int64",
"example": 1,
"description":"VLAN priority."
"description": "VLAN priority."
},
"ipDscp": {
"type": "byte",
"format": "byte",
"description":"IP DSCP (6 bits in ToS field)"
"description": "IP DSCP (6 bits in ToS field)"
},
"ipEcn": {
"type": "byte",
"format": "byte",
"description":"IP ECN (2 bits in ToS field)."
"description": "IP ECN (2 bits in ToS field)."
},
"protocol": {
"type": "uint16",
"format": "uint16",
"example": 1,
"description":"IP protocol"
"description": "IP protocol"
},
"ip": {
"type": "string",
"example": "10.1.1.0/24",
"description":"IP source address"
"description": "IP source address"
},
"tcpPort": {
"type": "integer",
"format": "uint16",
"example": 1,
"description":"TCP source address"
"description": "TCP source address"
},
"udpPort": {
"type": "uint16",
"format": "uint16",
"example": 1,
"description":"UDP source address"
"description": "UDP source address"
},
"sctpPort": {
"type": "uint16",
"format": "uint16",
"example": 1,
"description":"SCTP source address"
"description": "SCTP source address"
},
"icmpType": {
"type": "uint16",
"format": "uint16",
"example": 1,
"description":"Internet Control Message Protocol for IPV4 code (RFC0792)"
"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)"
"description": "Internet Control Message Protocol for IPV4 code (RFC0792)"
},
"flowLabel": {
"type": "Hex16",
"format": "Hex16",
"example": "0xffffe",
"description":"IPv6 Flow Label (RFC 6437)"
"description": "IPv6 Flow Label (RFC 6437)"
},
"icmpv6Type": {
"type": "uint16",
"format": "uint16",
"example": 1,
"description":"Internet Control Message Protocol for IPV6 type (RFC2463)"
"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)"
"description": "Internet Control Message Protocol for IPV6 code (RFC2463)"
},
"targetAddress": {
"type": "String",
"example": "10.1.1.0/24",
"description":"IPv6 Neighbor discovery target address"
"description": "IPv6 Neighbor discovery target address"
},
"label": {
"type": "int32",
"format": "int32",
"example": 1,
"description":"MPLS label"
"description": "MPLS label"
},
"exthdrFlags": {
"type": "int64",
"format": "int64",
"example": 1,
"description":"IPv6 extension header pseudo-field"
"description": "IPv6 extension header pseudo-field"
},
"lambda": {
"type": "int64",
"format": "int64",
"example": 1,
"description":"wavelength abstraction"
"description": "wavelength abstraction"
},
"gridType": {
"type": "String",
"example": "DWDM",
"description":"Type of wavelength grid"
"description": "Type of wavelength grid"
},
"channelSpacing": {
"type": "int64",
"format": "int64",
"example": 100,
"description":"Optical channel spacing"
"description": "Optical channel spacing"
},
"spacingMultiplier": {
"type": "integer",
"format": "int64",
"example": 4,
"description":"Optical channel spacing multiplier"
"description": "Optical channel spacing multiplier"
},
"slotGranularity": {
"type": "int64",
......@@ -312,42 +309,42 @@
"type": "integer",
"format": "int64",
"example": 1,
"description":"Optical channel signal ID"
"description": "Optical channel signal ID"
},
"tunnelId": {
"type": "int64",
"format": "int64",
"example": 5,
"description":"Tunnel ID"
"description": "Tunnel ID"
},
"ochSignalType": {
"type": "int64",
"format": "int64",
"example": 1,
"description":"Optical channel signal type"
"description": "Optical channel signal type"
},
"oduSignalId": {
"type": "int64",
"format": "int64",
"example": 1,
"description":"ODU (Optical channel Data Unit) signal ID."
"description": "ODU (Optical channel Data Unit) signal ID."
},
"tributaryPortNumber": {
"type": "int64",
"format": "int64",
"example": 11,
"description":"OPU (Optical channel Payload Unit) port number."
"description": "OPU (Optical channel Payload Unit) port number."
},
"tributarySlotLen": {
"type": "int64",
"format": "int64",
"example": 80,
"description":"OPU (Optical channel Payload Unit) slot length."
"description": "OPU (Optical channel Payload Unit) slot length."
},
"tributarySlotBitmap": {
"type": "array",
"title": "tributarySlotBitmap",
"description":"OPU (Optical channel Payload Unit) slot bitmap.",
"description": "OPU (Optical channel Payload Unit) slot bitmap.",
"required": [
"byte",
"port"
......@@ -362,7 +359,10 @@
"type": "int64",
"format": "int64",
"example": 4,
"description":"ODU (Optical channel Data Unit) signal type."
"description": "ODU (Optical channel Data Unit) signal type."
}
}
}
}
}
}
......
This diff is collapsed. Click to expand it.