[ONOS-3603] Implement REST API for Group query, insert, delete
* Implement decoding feature for GroupBucketCodec and GroupCodec
* Implement GroupsWebResource
* Add unit test for GroupBucketCodec and GroupCodec
* Add unit test for GroupsWebResource
* Add group insertion json example
* Add Swagger doc
Change-Id: Ie58cba2e1af996c7b8652a55d9ef0c27207beafc
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index e9fc7ac..d68b287 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -17,7 +17,6 @@
import com.codahale.metrics.Metric;
import com.google.common.collect.ImmutableSet;
-
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -35,11 +34,11 @@
import org.onosproject.net.Link;
import org.onosproject.net.Path;
import org.onosproject.net.Port;
+import org.onosproject.net.device.PortStatistics;
import org.onosproject.net.driver.Driver;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TableStatisticsEntry;
-import org.onosproject.net.device.PortStatistics;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
index c710514..c3819b3 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/GroupBucketCodec.java
@@ -15,14 +15,18 @@
*/
package org.onosproject.codec.impl;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroupBucket;
import org.onosproject.net.group.GroupBucket;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
/**
* Group bucket JSON codec.
@@ -36,6 +40,8 @@
private static final String WATCH_GROUP = "watchGroup";
private static final String PACKETS = "packets";
private static final String BYTES = "bytes";
+ private static final String MISSING_MEMBER_MESSAGE =
+ " member is required in Group";
@Override
public ObjectNode encode(GroupBucket bucket, CodecContext context) {
@@ -61,4 +67,59 @@
return result;
}
+
+ @Override
+ public GroupBucket decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ // build traffic treatment
+ ObjectNode treatmentJson = get(json, TREATMENT);
+ TrafficTreatment trafficTreatment = null;
+ if (treatmentJson != null) {
+ JsonCodec<TrafficTreatment> treatmentCodec =
+ context.codec(TrafficTreatment.class);
+ trafficTreatment = treatmentCodec.decode(treatmentJson, context);
+ }
+
+ // parse group type
+ String type = nullIsIllegal(json.get(TYPE), TYPE + MISSING_MEMBER_MESSAGE).asText();
+ GroupBucket groupBucket = null;
+
+ switch (type) {
+ case "SELECT":
+ // parse weight
+ int weightInt = nullIsIllegal(json.get(WEIGHT), WEIGHT + MISSING_MEMBER_MESSAGE).asInt();
+
+ groupBucket =
+ DefaultGroupBucket.createSelectGroupBucket(trafficTreatment, (short) weightInt);
+ break;
+ case "INDIRECT":
+ groupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(trafficTreatment);
+ break;
+ case "ALL":
+ groupBucket =
+ DefaultGroupBucket.createAllGroupBucket(trafficTreatment);
+ break;
+ case "FAILOVER":
+ // parse watchPort
+ PortNumber watchPort = PortNumber.portNumber(nullIsIllegal(json.get(WATCH_PORT),
+ WATCH_PORT + MISSING_MEMBER_MESSAGE).asText());
+
+ // parse watchGroup
+ int groupIdInt = nullIsIllegal(json.get(WATCH_GROUP),
+ WATCH_GROUP + MISSING_MEMBER_MESSAGE).asInt();
+ GroupId watchGroup = new DefaultGroupId((short) groupIdInt);
+
+ groupBucket =
+ DefaultGroupBucket.createFailoverGroupBucket(trafficTreatment, watchPort, watchGroup);
+ break;
+ default:
+ DefaultGroupBucket.createAllGroupBucket(trafficTreatment);
+ }
+
+ return groupBucket;
+ }
}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
index a2f33ce..6a7e404 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/GroupCodec.java
@@ -15,20 +15,40 @@
*/
package org.onosproject.codec.impl;
-import org.onosproject.codec.CodecContext;
-import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.group.Group;
-import org.onosproject.net.group.GroupBucket;
-
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
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.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.slf4j.LoggerFactory.getLogger;
/**
* Group JSON codec.
*/
public final class GroupCodec extends JsonCodec<Group> {
+ private final Logger log = getLogger(getClass());
+
// JSON field names
private static final String ID = "id";
private static final String STATE = "state";
@@ -37,11 +57,15 @@
private static final String BYTES = "bytes";
private static final String REFERENCE_COUNT = "referenceCount";
private static final String TYPE = "type";
+ private static final String GROUP_ID = "groupId";
private static final String DEVICE_ID = "deviceId";
private static final String APP_ID = "appId";
- private static final String APP_COOKIE = "appCookie";
- private static final String GIVEN_GROUP_ID = "givenGroupId";
+ private static final String APP_COOKIE = "appCookie";
+ private static final String GIVEN_GROUP_ID = "givenGroupId";
private static final String BUCKETS = "buckets";
+ private static final String MISSING_MEMBER_MESSAGE =
+ " member is required in Group";
+ public static final String REST_APP_ID = "org.onosproject.rest";
@Override
public ObjectNode encode(Group group, CodecContext context) {
@@ -70,10 +94,81 @@
ArrayNode buckets = context.mapper().createArrayNode();
group.buckets().buckets().forEach(bucket -> {
- ObjectNode bucketJson = context.codec(GroupBucket.class).encode(bucket, context);
- buckets.add(bucketJson);
- });
+ ObjectNode bucketJson = context.codec(GroupBucket.class).encode(bucket, context);
+ buckets.add(bucketJson);
+ });
result.set(BUCKETS, buckets);
return result;
}
+
+ @Override
+ public Group decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ final JsonCodec<GroupBucket> groupBucketCodec = context.codec(GroupBucket.class);
+ CoreService coreService = context.getService(CoreService.class);
+
+ // parse group id
+ int groupIdInt = nullIsIllegal(json.get(GROUP_ID),
+ GROUP_ID + MISSING_MEMBER_MESSAGE).asInt();
+ GroupId groupId = new DefaultGroupId((short) groupIdInt);
+
+ // parse group key (appCookie)
+ String groupKeyStr = nullIsIllegal(json.get(APP_COOKIE),
+ APP_COOKIE + MISSING_MEMBER_MESSAGE).asText();
+ GroupKey groupKey = new DefaultGroupKey(groupKeyStr.getBytes());
+
+ // parse device id
+ DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID),
+ DEVICE_ID + MISSING_MEMBER_MESSAGE).asText());
+
+ // application id
+ ApplicationId appId = coreService.registerApplication(REST_APP_ID);
+
+ // parse group type
+ String type = nullIsIllegal(json.get(TYPE),
+ TYPE + MISSING_MEMBER_MESSAGE).asText();
+ GroupDescription.Type groupType = null;
+
+ switch (type) {
+ case "SELECT":
+ groupType = Group.Type.SELECT;
+ break;
+ case "INDIRECT":
+ groupType = Group.Type.INDIRECT;
+ break;
+ case "ALL":
+ groupType = Group.Type.ALL;
+ break;
+ case "FAILOVER":
+ groupType = Group.Type.FAILOVER;
+ break;
+ default:
+ log.warn("The requested type {} is not defined for group.", type);
+ return null;
+ }
+
+ // parse group buckets
+ // TODO: make sure that INDIRECT group only has one bucket
+ GroupBuckets buckets = null;
+ List<GroupBucket> groupBucketList = new ArrayList<>();
+ JsonNode bucketsJson = json.get(BUCKETS);
+ checkNotNull(bucketsJson);
+ if (bucketsJson != null) {
+ IntStream.range(0, bucketsJson.size())
+ .forEach(i -> {
+ ObjectNode bucketJson = get(bucketsJson, i);
+ bucketJson.put("type", type);
+ groupBucketList.add(groupBucketCodec.decode(bucketJson, context));
+ });
+ buckets = new GroupBuckets(groupBucketList);
+ }
+
+ GroupDescription groupDescription = new DefaultGroupDescription(deviceId,
+ groupType, buckets, groupKey, groupIdInt, appId);
+
+ return new DefaultGroup(groupId, groupDescription);
+ }
}