Committed by
Ray Milkey
[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
Showing
11 changed files
with
1126 additions
and
9 deletions
... | @@ -17,7 +17,6 @@ package org.onosproject.codec.impl; | ... | @@ -17,7 +17,6 @@ package org.onosproject.codec.impl; |
17 | 17 | ||
18 | import com.codahale.metrics.Metric; | 18 | import com.codahale.metrics.Metric; |
19 | import com.google.common.collect.ImmutableSet; | 19 | import com.google.common.collect.ImmutableSet; |
20 | - | ||
21 | import org.apache.felix.scr.annotations.Activate; | 20 | import org.apache.felix.scr.annotations.Activate; |
22 | import org.apache.felix.scr.annotations.Component; | 21 | import org.apache.felix.scr.annotations.Component; |
23 | import org.apache.felix.scr.annotations.Deactivate; | 22 | import org.apache.felix.scr.annotations.Deactivate; |
... | @@ -35,11 +34,11 @@ import org.onosproject.net.HostLocation; | ... | @@ -35,11 +34,11 @@ import org.onosproject.net.HostLocation; |
35 | import org.onosproject.net.Link; | 34 | import org.onosproject.net.Link; |
36 | import org.onosproject.net.Path; | 35 | import org.onosproject.net.Path; |
37 | import org.onosproject.net.Port; | 36 | import org.onosproject.net.Port; |
37 | +import org.onosproject.net.device.PortStatistics; | ||
38 | import org.onosproject.net.driver.Driver; | 38 | import org.onosproject.net.driver.Driver; |
39 | import org.onosproject.net.flow.FlowEntry; | 39 | import org.onosproject.net.flow.FlowEntry; |
40 | import org.onosproject.net.flow.FlowRule; | 40 | import org.onosproject.net.flow.FlowRule; |
41 | import org.onosproject.net.flow.TableStatisticsEntry; | 41 | import org.onosproject.net.flow.TableStatisticsEntry; |
42 | -import org.onosproject.net.device.PortStatistics; | ||
43 | import org.onosproject.net.flow.TrafficSelector; | 42 | import org.onosproject.net.flow.TrafficSelector; |
44 | import org.onosproject.net.flow.TrafficTreatment; | 43 | import org.onosproject.net.flow.TrafficTreatment; |
45 | import org.onosproject.net.flow.criteria.Criterion; | 44 | import org.onosproject.net.flow.criteria.Criterion; | ... | ... |
... | @@ -15,14 +15,18 @@ | ... | @@ -15,14 +15,18 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.codec.impl; | 16 | package org.onosproject.codec.impl; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
18 | import org.onosproject.codec.CodecContext; | 19 | import org.onosproject.codec.CodecContext; |
19 | import org.onosproject.codec.JsonCodec; | 20 | import org.onosproject.codec.JsonCodec; |
21 | +import org.onosproject.core.DefaultGroupId; | ||
22 | +import org.onosproject.core.GroupId; | ||
23 | +import org.onosproject.net.PortNumber; | ||
20 | import org.onosproject.net.flow.TrafficTreatment; | 24 | import org.onosproject.net.flow.TrafficTreatment; |
25 | +import org.onosproject.net.group.DefaultGroupBucket; | ||
21 | import org.onosproject.net.group.GroupBucket; | 26 | import org.onosproject.net.group.GroupBucket; |
22 | 27 | ||
23 | -import com.fasterxml.jackson.databind.node.ObjectNode; | ||
24 | - | ||
25 | import static com.google.common.base.Preconditions.checkNotNull; | 28 | import static com.google.common.base.Preconditions.checkNotNull; |
29 | +import static org.onlab.util.Tools.nullIsIllegal; | ||
26 | 30 | ||
27 | /** | 31 | /** |
28 | * Group bucket JSON codec. | 32 | * Group bucket JSON codec. |
... | @@ -36,6 +40,8 @@ public class GroupBucketCodec extends JsonCodec<GroupBucket> { | ... | @@ -36,6 +40,8 @@ public class GroupBucketCodec extends JsonCodec<GroupBucket> { |
36 | private static final String WATCH_GROUP = "watchGroup"; | 40 | private static final String WATCH_GROUP = "watchGroup"; |
37 | private static final String PACKETS = "packets"; | 41 | private static final String PACKETS = "packets"; |
38 | private static final String BYTES = "bytes"; | 42 | private static final String BYTES = "bytes"; |
43 | + private static final String MISSING_MEMBER_MESSAGE = | ||
44 | + " member is required in Group"; | ||
39 | 45 | ||
40 | @Override | 46 | @Override |
41 | public ObjectNode encode(GroupBucket bucket, CodecContext context) { | 47 | public ObjectNode encode(GroupBucket bucket, CodecContext context) { |
... | @@ -61,4 +67,59 @@ public class GroupBucketCodec extends JsonCodec<GroupBucket> { | ... | @@ -61,4 +67,59 @@ public class GroupBucketCodec extends JsonCodec<GroupBucket> { |
61 | 67 | ||
62 | return result; | 68 | return result; |
63 | } | 69 | } |
70 | + | ||
71 | + @Override | ||
72 | + public GroupBucket decode(ObjectNode json, CodecContext context) { | ||
73 | + if (json == null || !json.isObject()) { | ||
74 | + return null; | ||
75 | + } | ||
76 | + | ||
77 | + // build traffic treatment | ||
78 | + ObjectNode treatmentJson = get(json, TREATMENT); | ||
79 | + TrafficTreatment trafficTreatment = null; | ||
80 | + if (treatmentJson != null) { | ||
81 | + JsonCodec<TrafficTreatment> treatmentCodec = | ||
82 | + context.codec(TrafficTreatment.class); | ||
83 | + trafficTreatment = treatmentCodec.decode(treatmentJson, context); | ||
84 | + } | ||
85 | + | ||
86 | + // parse group type | ||
87 | + String type = nullIsIllegal(json.get(TYPE), TYPE + MISSING_MEMBER_MESSAGE).asText(); | ||
88 | + GroupBucket groupBucket = null; | ||
89 | + | ||
90 | + switch (type) { | ||
91 | + case "SELECT": | ||
92 | + // parse weight | ||
93 | + int weightInt = nullIsIllegal(json.get(WEIGHT), WEIGHT + MISSING_MEMBER_MESSAGE).asInt(); | ||
94 | + | ||
95 | + groupBucket = | ||
96 | + DefaultGroupBucket.createSelectGroupBucket(trafficTreatment, (short) weightInt); | ||
97 | + break; | ||
98 | + case "INDIRECT": | ||
99 | + groupBucket = | ||
100 | + DefaultGroupBucket.createIndirectGroupBucket(trafficTreatment); | ||
101 | + break; | ||
102 | + case "ALL": | ||
103 | + groupBucket = | ||
104 | + DefaultGroupBucket.createAllGroupBucket(trafficTreatment); | ||
105 | + break; | ||
106 | + case "FAILOVER": | ||
107 | + // parse watchPort | ||
108 | + PortNumber watchPort = PortNumber.portNumber(nullIsIllegal(json.get(WATCH_PORT), | ||
109 | + WATCH_PORT + MISSING_MEMBER_MESSAGE).asText()); | ||
110 | + | ||
111 | + // parse watchGroup | ||
112 | + int groupIdInt = nullIsIllegal(json.get(WATCH_GROUP), | ||
113 | + WATCH_GROUP + MISSING_MEMBER_MESSAGE).asInt(); | ||
114 | + GroupId watchGroup = new DefaultGroupId((short) groupIdInt); | ||
115 | + | ||
116 | + groupBucket = | ||
117 | + DefaultGroupBucket.createFailoverGroupBucket(trafficTreatment, watchPort, watchGroup); | ||
118 | + break; | ||
119 | + default: | ||
120 | + DefaultGroupBucket.createAllGroupBucket(trafficTreatment); | ||
121 | + } | ||
122 | + | ||
123 | + return groupBucket; | ||
124 | + } | ||
64 | } | 125 | } | ... | ... |
... | @@ -15,20 +15,40 @@ | ... | @@ -15,20 +15,40 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.codec.impl; | 16 | package org.onosproject.codec.impl; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
20 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
18 | import org.onosproject.codec.CodecContext; | 21 | import org.onosproject.codec.CodecContext; |
19 | import org.onosproject.codec.JsonCodec; | 22 | import org.onosproject.codec.JsonCodec; |
23 | +import org.onosproject.core.ApplicationId; | ||
24 | +import org.onosproject.core.CoreService; | ||
25 | +import org.onosproject.core.DefaultGroupId; | ||
26 | +import org.onosproject.core.GroupId; | ||
27 | +import org.onosproject.net.DeviceId; | ||
28 | +import org.onosproject.net.group.DefaultGroup; | ||
29 | +import org.onosproject.net.group.DefaultGroupDescription; | ||
30 | +import org.onosproject.net.group.DefaultGroupKey; | ||
20 | import org.onosproject.net.group.Group; | 31 | import org.onosproject.net.group.Group; |
21 | import org.onosproject.net.group.GroupBucket; | 32 | import org.onosproject.net.group.GroupBucket; |
33 | +import org.onosproject.net.group.GroupBuckets; | ||
34 | +import org.onosproject.net.group.GroupDescription; | ||
35 | +import org.onosproject.net.group.GroupKey; | ||
36 | +import org.slf4j.Logger; | ||
22 | 37 | ||
23 | -import com.fasterxml.jackson.databind.node.ArrayNode; | 38 | +import java.util.ArrayList; |
24 | -import com.fasterxml.jackson.databind.node.ObjectNode; | 39 | +import java.util.List; |
40 | +import java.util.stream.IntStream; | ||
25 | 41 | ||
26 | import static com.google.common.base.Preconditions.checkNotNull; | 42 | import static com.google.common.base.Preconditions.checkNotNull; |
43 | +import static org.onlab.util.Tools.nullIsIllegal; | ||
44 | +import static org.slf4j.LoggerFactory.getLogger; | ||
27 | 45 | ||
28 | /** | 46 | /** |
29 | * Group JSON codec. | 47 | * Group JSON codec. |
30 | */ | 48 | */ |
31 | public final class GroupCodec extends JsonCodec<Group> { | 49 | public final class GroupCodec extends JsonCodec<Group> { |
50 | + private final Logger log = getLogger(getClass()); | ||
51 | + | ||
32 | // JSON field names | 52 | // JSON field names |
33 | private static final String ID = "id"; | 53 | private static final String ID = "id"; |
34 | private static final String STATE = "state"; | 54 | private static final String STATE = "state"; |
... | @@ -37,11 +57,15 @@ public final class GroupCodec extends JsonCodec<Group> { | ... | @@ -37,11 +57,15 @@ public final class GroupCodec extends JsonCodec<Group> { |
37 | private static final String BYTES = "bytes"; | 57 | private static final String BYTES = "bytes"; |
38 | private static final String REFERENCE_COUNT = "referenceCount"; | 58 | private static final String REFERENCE_COUNT = "referenceCount"; |
39 | private static final String TYPE = "type"; | 59 | private static final String TYPE = "type"; |
60 | + private static final String GROUP_ID = "groupId"; | ||
40 | private static final String DEVICE_ID = "deviceId"; | 61 | private static final String DEVICE_ID = "deviceId"; |
41 | private static final String APP_ID = "appId"; | 62 | private static final String APP_ID = "appId"; |
42 | private static final String APP_COOKIE = "appCookie"; | 63 | private static final String APP_COOKIE = "appCookie"; |
43 | private static final String GIVEN_GROUP_ID = "givenGroupId"; | 64 | private static final String GIVEN_GROUP_ID = "givenGroupId"; |
44 | private static final String BUCKETS = "buckets"; | 65 | private static final String BUCKETS = "buckets"; |
66 | + private static final String MISSING_MEMBER_MESSAGE = | ||
67 | + " member is required in Group"; | ||
68 | + public static final String REST_APP_ID = "org.onosproject.rest"; | ||
45 | 69 | ||
46 | @Override | 70 | @Override |
47 | public ObjectNode encode(Group group, CodecContext context) { | 71 | public ObjectNode encode(Group group, CodecContext context) { |
... | @@ -76,4 +100,75 @@ public final class GroupCodec extends JsonCodec<Group> { | ... | @@ -76,4 +100,75 @@ public final class GroupCodec extends JsonCodec<Group> { |
76 | result.set(BUCKETS, buckets); | 100 | result.set(BUCKETS, buckets); |
77 | return result; | 101 | return result; |
78 | } | 102 | } |
103 | + | ||
104 | + @Override | ||
105 | + public Group decode(ObjectNode json, CodecContext context) { | ||
106 | + if (json == null || !json.isObject()) { | ||
107 | + return null; | ||
108 | + } | ||
109 | + | ||
110 | + final JsonCodec<GroupBucket> groupBucketCodec = context.codec(GroupBucket.class); | ||
111 | + CoreService coreService = context.getService(CoreService.class); | ||
112 | + | ||
113 | + // parse group id | ||
114 | + int groupIdInt = nullIsIllegal(json.get(GROUP_ID), | ||
115 | + GROUP_ID + MISSING_MEMBER_MESSAGE).asInt(); | ||
116 | + GroupId groupId = new DefaultGroupId((short) groupIdInt); | ||
117 | + | ||
118 | + // parse group key (appCookie) | ||
119 | + String groupKeyStr = nullIsIllegal(json.get(APP_COOKIE), | ||
120 | + APP_COOKIE + MISSING_MEMBER_MESSAGE).asText(); | ||
121 | + GroupKey groupKey = new DefaultGroupKey(groupKeyStr.getBytes()); | ||
122 | + | ||
123 | + // parse device id | ||
124 | + DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID), | ||
125 | + DEVICE_ID + MISSING_MEMBER_MESSAGE).asText()); | ||
126 | + | ||
127 | + // application id | ||
128 | + ApplicationId appId = coreService.registerApplication(REST_APP_ID); | ||
129 | + | ||
130 | + // parse group type | ||
131 | + String type = nullIsIllegal(json.get(TYPE), | ||
132 | + TYPE + MISSING_MEMBER_MESSAGE).asText(); | ||
133 | + GroupDescription.Type groupType = null; | ||
134 | + | ||
135 | + switch (type) { | ||
136 | + case "SELECT": | ||
137 | + groupType = Group.Type.SELECT; | ||
138 | + break; | ||
139 | + case "INDIRECT": | ||
140 | + groupType = Group.Type.INDIRECT; | ||
141 | + break; | ||
142 | + case "ALL": | ||
143 | + groupType = Group.Type.ALL; | ||
144 | + break; | ||
145 | + case "FAILOVER": | ||
146 | + groupType = Group.Type.FAILOVER; | ||
147 | + break; | ||
148 | + default: | ||
149 | + log.warn("The requested type {} is not defined for group.", type); | ||
150 | + return null; | ||
151 | + } | ||
152 | + | ||
153 | + // parse group buckets | ||
154 | + // TODO: make sure that INDIRECT group only has one bucket | ||
155 | + GroupBuckets buckets = null; | ||
156 | + List<GroupBucket> groupBucketList = new ArrayList<>(); | ||
157 | + JsonNode bucketsJson = json.get(BUCKETS); | ||
158 | + checkNotNull(bucketsJson); | ||
159 | + if (bucketsJson != null) { | ||
160 | + IntStream.range(0, bucketsJson.size()) | ||
161 | + .forEach(i -> { | ||
162 | + ObjectNode bucketJson = get(bucketsJson, i); | ||
163 | + bucketJson.put("type", type); | ||
164 | + groupBucketList.add(groupBucketCodec.decode(bucketJson, context)); | ||
165 | + }); | ||
166 | + buckets = new GroupBuckets(groupBucketList); | ||
167 | + } | ||
168 | + | ||
169 | + GroupDescription groupDescription = new DefaultGroupDescription(deviceId, | ||
170 | + groupType, buckets, groupKey, groupIdInt, appId); | ||
171 | + | ||
172 | + return new DefaultGroup(groupId, groupDescription); | ||
173 | + } | ||
79 | } | 174 | } | ... | ... |
... | @@ -15,21 +15,37 @@ | ... | @@ -15,21 +15,37 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.codec.impl; | 16 | package org.onosproject.codec.impl; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
20 | +import com.google.common.collect.ImmutableList; | ||
21 | +import org.junit.Before; | ||
18 | import org.junit.Test; | 22 | import org.junit.Test; |
23 | +import org.onosproject.codec.JsonCodec; | ||
24 | +import org.onosproject.core.CoreService; | ||
19 | import org.onosproject.core.DefaultGroupId; | 25 | import org.onosproject.core.DefaultGroupId; |
20 | import org.onosproject.net.NetTestTools; | 26 | import org.onosproject.net.NetTestTools; |
27 | +import org.onosproject.net.PortNumber; | ||
21 | import org.onosproject.net.flow.DefaultTrafficTreatment; | 28 | import org.onosproject.net.flow.DefaultTrafficTreatment; |
29 | +import org.onosproject.net.flow.instructions.Instruction; | ||
30 | +import org.onosproject.net.flow.instructions.Instructions; | ||
22 | import org.onosproject.net.group.DefaultGroup; | 31 | import org.onosproject.net.group.DefaultGroup; |
23 | import org.onosproject.net.group.DefaultGroupBucket; | 32 | import org.onosproject.net.group.DefaultGroupBucket; |
33 | +import org.onosproject.net.group.Group; | ||
24 | import org.onosproject.net.group.GroupBucket; | 34 | import org.onosproject.net.group.GroupBucket; |
25 | import org.onosproject.net.group.GroupBuckets; | 35 | import org.onosproject.net.group.GroupBuckets; |
26 | import org.onosproject.net.group.GroupDescription; | 36 | import org.onosproject.net.group.GroupDescription; |
27 | 37 | ||
28 | -import com.fasterxml.jackson.databind.node.ObjectNode; | 38 | +import java.io.IOException; |
29 | -import com.google.common.collect.ImmutableList; | 39 | +import java.io.InputStream; |
30 | 40 | ||
41 | +import static org.easymock.EasyMock.createMock; | ||
42 | +import static org.easymock.EasyMock.expect; | ||
43 | +import static org.easymock.EasyMock.replay; | ||
31 | import static org.hamcrest.MatcherAssert.assertThat; | 44 | import static org.hamcrest.MatcherAssert.assertThat; |
45 | +import static org.hamcrest.Matchers.is; | ||
46 | +import static org.hamcrest.Matchers.notNullValue; | ||
32 | import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup; | 47 | import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup; |
48 | +import static org.onosproject.net.NetTestTools.APP_ID; | ||
33 | 49 | ||
34 | /** | 50 | /** |
35 | * Group codec unit tests. | 51 | * Group codec unit tests. |
... | @@ -37,8 +53,28 @@ import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup; | ... | @@ -37,8 +53,28 @@ import static org.onosproject.codec.impl.GroupJsonMatcher.matchesGroup; |
37 | 53 | ||
38 | public class GroupCodecTest { | 54 | public class GroupCodecTest { |
39 | 55 | ||
56 | + MockCodecContext context; | ||
57 | + JsonCodec<Group> groupCodec; | ||
58 | + final CoreService mockCoreService = createMock(CoreService.class); | ||
59 | + | ||
60 | + /** | ||
61 | + * Sets up for each test. Creates a context and fetches the flow rule | ||
62 | + * codec. | ||
63 | + */ | ||
64 | + @Before | ||
65 | + public void setUp() { | ||
66 | + context = new MockCodecContext(); | ||
67 | + groupCodec = context.codec(Group.class); | ||
68 | + assertThat(groupCodec, notNullValue()); | ||
69 | + | ||
70 | + expect(mockCoreService.registerApplication(GroupCodec.REST_APP_ID)) | ||
71 | + .andReturn(APP_ID).anyTimes(); | ||
72 | + replay(mockCoreService); | ||
73 | + context.registerService(CoreService.class, mockCoreService); | ||
74 | + } | ||
75 | + | ||
40 | @Test | 76 | @Test |
41 | - public void codecTest() { | 77 | + public void codecEncodeTest() { |
42 | GroupBucket bucket1 = DefaultGroupBucket | 78 | GroupBucket bucket1 = DefaultGroupBucket |
43 | .createSelectGroupBucket(DefaultTrafficTreatment.emptyTreatment()); | 79 | .createSelectGroupBucket(DefaultTrafficTreatment.emptyTreatment()); |
44 | GroupBucket bucket2 = DefaultGroupBucket | 80 | GroupBucket bucket2 = DefaultGroupBucket |
... | @@ -58,4 +94,48 @@ public class GroupCodecTest { | ... | @@ -58,4 +94,48 @@ public class GroupCodecTest { |
58 | 94 | ||
59 | assertThat(groupJson, matchesGroup(group)); | 95 | assertThat(groupJson, matchesGroup(group)); |
60 | } | 96 | } |
97 | + | ||
98 | + @Test | ||
99 | + public void codecDecodeTest() throws IOException { | ||
100 | + Group group = getGroup("simple-group.json"); | ||
101 | + checkCommonData(group); | ||
102 | + | ||
103 | + assertThat(group.buckets().buckets().size(), is(1)); | ||
104 | + GroupBucket groupBucket = group.buckets().buckets().get(0); | ||
105 | + assertThat(groupBucket.type().toString(), is("ALL")); | ||
106 | + assertThat(groupBucket.treatment().allInstructions().size(), is(1)); | ||
107 | + Instruction instruction1 = groupBucket.treatment().allInstructions().get(0); | ||
108 | + assertThat(instruction1.type(), is(Instruction.Type.OUTPUT)); | ||
109 | + assertThat(((Instructions.OutputInstruction) instruction1).port(), is(PortNumber.portNumber(2))); | ||
110 | + } | ||
111 | + | ||
112 | + /** | ||
113 | + * Checks that the data shared by all the resource is correct for a given group. | ||
114 | + * | ||
115 | + * @param group group to check | ||
116 | + */ | ||
117 | + private void checkCommonData(Group group) { | ||
118 | + assertThat(group.appId(), is(APP_ID)); | ||
119 | + assertThat(group.deviceId().toString(), is("of:0000000000000001")); | ||
120 | + assertThat(group.type().toString(), is("ALL")); | ||
121 | + assertThat(group.appCookie().key(), is("1".getBytes())); | ||
122 | + assertThat(group.id().id(), is(1)); | ||
123 | + } | ||
124 | + | ||
125 | + /** | ||
126 | + * Reads in a group from the given resource and decodes it. | ||
127 | + * | ||
128 | + * @param resourceName resource to use to read the JSON for the rule | ||
129 | + * @return decoded group | ||
130 | + * @throws IOException if processing the resource fails | ||
131 | + */ | ||
132 | + private Group getGroup(String resourceName) throws IOException { | ||
133 | + InputStream jsonStream = GroupCodecTest.class | ||
134 | + .getResourceAsStream(resourceName); | ||
135 | + JsonNode json = context.mapper().readTree(jsonStream); | ||
136 | + assertThat(json, notNullValue()); | ||
137 | + Group group = groupCodec.decode((ObjectNode) json, context); | ||
138 | + assertThat(group, notNullValue()); | ||
139 | + return group; | ||
140 | + } | ||
61 | } | 141 | } | ... | ... |
1 | +{ | ||
2 | + "type": "ALL", | ||
3 | + "deviceId": "of:0000000000000001", | ||
4 | + "appCookie": "1", | ||
5 | + "groupId": "1", | ||
6 | + "buckets": [ | ||
7 | + { | ||
8 | + "treatment": { | ||
9 | + "instructions": [ | ||
10 | + { | ||
11 | + "type": "OUTPUT", | ||
12 | + "port": 2 | ||
13 | + } | ||
14 | + ] | ||
15 | + } | ||
16 | + } | ||
17 | + ] | ||
18 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -37,6 +37,7 @@ public class CoreWebApplication extends AbstractWebApplication { | ... | @@ -37,6 +37,7 @@ public class CoreWebApplication extends AbstractWebApplication { |
37 | HostsWebResource.class, | 37 | HostsWebResource.class, |
38 | IntentsWebResource.class, | 38 | IntentsWebResource.class, |
39 | FlowsWebResource.class, | 39 | FlowsWebResource.class, |
40 | + GroupsWebResource.class, | ||
40 | TopologyWebResource.class, | 41 | TopologyWebResource.class, |
41 | ConfigWebResource.class, | 42 | ConfigWebResource.class, |
42 | PathsWebResource.class, | 43 | PathsWebResource.class, | ... | ... |
1 | +/* | ||
2 | + * Copyright 2014-2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.onosproject.rest.resources; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
20 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
21 | +import org.onosproject.net.Device; | ||
22 | +import org.onosproject.net.DeviceId; | ||
23 | +import org.onosproject.net.device.DeviceService; | ||
24 | +import org.onosproject.net.group.DefaultGroupDescription; | ||
25 | +import org.onosproject.net.group.DefaultGroupKey; | ||
26 | +import org.onosproject.net.group.Group; | ||
27 | +import org.onosproject.net.group.GroupDescription; | ||
28 | +import org.onosproject.net.group.GroupKey; | ||
29 | +import org.onosproject.net.group.GroupService; | ||
30 | +import org.onosproject.rest.AbstractWebResource; | ||
31 | + | ||
32 | +import javax.ws.rs.Consumes; | ||
33 | +import javax.ws.rs.DELETE; | ||
34 | +import javax.ws.rs.GET; | ||
35 | +import javax.ws.rs.POST; | ||
36 | +import javax.ws.rs.Path; | ||
37 | +import javax.ws.rs.PathParam; | ||
38 | +import javax.ws.rs.Produces; | ||
39 | +import javax.ws.rs.core.MediaType; | ||
40 | +import javax.ws.rs.core.Response; | ||
41 | +import java.io.IOException; | ||
42 | +import java.io.InputStream; | ||
43 | +import java.net.URI; | ||
44 | +import java.net.URISyntaxException; | ||
45 | + | ||
46 | +/** | ||
47 | + * Query and program group rules. | ||
48 | + */ | ||
49 | + | ||
50 | +@Path("groups") | ||
51 | +public class GroupsWebResource extends AbstractWebResource { | ||
52 | + public static final String DEVICE_INVALID = "Invalid deviceId in group creation request"; | ||
53 | + | ||
54 | + final GroupService groupService = get(GroupService.class); | ||
55 | + final ObjectNode root = mapper().createObjectNode(); | ||
56 | + final ArrayNode groupsNode = root.putArray("groups"); | ||
57 | + | ||
58 | + /** | ||
59 | + * Returns all groups of all devices. | ||
60 | + * @onos.rsModel Groups | ||
61 | + * @return array of all the groups in the system | ||
62 | + */ | ||
63 | + @GET | ||
64 | + @Produces(MediaType.APPLICATION_JSON) | ||
65 | + public Response getGroups() { | ||
66 | + final Iterable<Device> devices = get(DeviceService.class).getDevices(); | ||
67 | + devices.forEach(device -> { | ||
68 | + final Iterable<Group> groups = groupService.getGroups(device.id()); | ||
69 | + if (groups != null) { | ||
70 | + groups.forEach(group -> groupsNode.add(codec(Group.class).encode(group, this))); | ||
71 | + } | ||
72 | + }); | ||
73 | + | ||
74 | + return ok(root).build(); | ||
75 | + } | ||
76 | + | ||
77 | + /** | ||
78 | + * Returns all groups associated with the given device. | ||
79 | + * | ||
80 | + * @param deviceId device identifier | ||
81 | + * @onos.rsModel Groups | ||
82 | + * @return array of all the groups in the system | ||
83 | + */ | ||
84 | + @GET | ||
85 | + @Produces(MediaType.APPLICATION_JSON) | ||
86 | + @Path("{deviceId}") | ||
87 | + public Response getGroupsByDeviceId(@PathParam("deviceId") String deviceId) { | ||
88 | + final Iterable<Group> groups = groupService.getGroups(DeviceId.deviceId(deviceId)); | ||
89 | + | ||
90 | + groups.forEach(group -> groupsNode.add(codec(Group.class).encode(group, this))); | ||
91 | + | ||
92 | + return ok(root).build(); | ||
93 | + } | ||
94 | + | ||
95 | + /** | ||
96 | + * Create new group rule. Creates and installs a new group rule for the | ||
97 | + * specified device. | ||
98 | + * | ||
99 | + * @param deviceId device identifier | ||
100 | + * @param stream group rule JSON | ||
101 | + * @onos.rsModel GroupsPost | ||
102 | + * @return status of the request - CREATED if the JSON is correct, | ||
103 | + * BAD_REQUEST if the JSON is invalid | ||
104 | + */ | ||
105 | + @POST | ||
106 | + @Path("{deviceId}") | ||
107 | + @Consumes(MediaType.APPLICATION_JSON) | ||
108 | + @Produces(MediaType.APPLICATION_JSON) | ||
109 | + public Response createGroup(@PathParam("deviceId") String deviceId, | ||
110 | + InputStream stream) { | ||
111 | + URI location; | ||
112 | + try { | ||
113 | + ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream); | ||
114 | + JsonNode specifiedDeviceId = jsonTree.get("deviceId"); | ||
115 | + | ||
116 | + if (specifiedDeviceId != null && | ||
117 | + !specifiedDeviceId.asText().equals(deviceId)) { | ||
118 | + throw new IllegalArgumentException(DEVICE_INVALID); | ||
119 | + } | ||
120 | + jsonTree.put("deviceId", deviceId); | ||
121 | + Group group = codec(Group.class).decode(jsonTree, this); | ||
122 | + GroupDescription description = new DefaultGroupDescription( | ||
123 | + group.deviceId(), group.type(), group.buckets(), | ||
124 | + group.appCookie(), group.id().id(), group.appId()); | ||
125 | + groupService.addGroup(description); | ||
126 | + location = new URI(Long.toString(group.id().id())); | ||
127 | + } catch (IOException | URISyntaxException ex) { | ||
128 | + throw new IllegalArgumentException(ex); | ||
129 | + } | ||
130 | + | ||
131 | + return Response | ||
132 | + .created(location) | ||
133 | + .build(); | ||
134 | + } | ||
135 | + | ||
136 | + /** | ||
137 | + * Removes the specified group. | ||
138 | + * | ||
139 | + * @param deviceId device identifier | ||
140 | + * @param appCookie application cookie to be used for lookup | ||
141 | + */ | ||
142 | + @DELETE | ||
143 | + @Produces(MediaType.APPLICATION_JSON) | ||
144 | + @Path("{deviceId}/{appCookie}") | ||
145 | + public void deleteGroupByDeviceIdAndAppCookie(@PathParam("deviceId") String deviceId, | ||
146 | + @PathParam("appCookie") String appCookie) { | ||
147 | + DeviceId deviceIdInstance = DeviceId.deviceId(deviceId); | ||
148 | + GroupKey appCookieInstance = new DefaultGroupKey(appCookie.getBytes()); | ||
149 | + | ||
150 | + groupService.removeGroup(deviceIdInstance, appCookieInstance, null); | ||
151 | + } | ||
152 | +} |
1 | +{ | ||
2 | + "type": "object", | ||
3 | + "title": "groups", | ||
4 | + "required": [ | ||
5 | + "groups" | ||
6 | + ], | ||
7 | + "properties": { | ||
8 | + "groups": { | ||
9 | + "type": "array", | ||
10 | + "xml": { | ||
11 | + "name": "groups", | ||
12 | + "wrapped": true | ||
13 | + }, | ||
14 | + "items": { | ||
15 | + "type": "object", | ||
16 | + "title": "group", | ||
17 | + "required": [ | ||
18 | + "id", | ||
19 | + "state", | ||
20 | + "life", | ||
21 | + "packets", | ||
22 | + "bytes", | ||
23 | + "referenceCount", | ||
24 | + "type", | ||
25 | + "deviceId", | ||
26 | + "buckets" | ||
27 | + ], | ||
28 | + "properties": { | ||
29 | + "id": { | ||
30 | + "type": "string", | ||
31 | + "example": "1" | ||
32 | + }, | ||
33 | + "state": { | ||
34 | + "type": "string", | ||
35 | + "example": "PENDING_ADD" | ||
36 | + }, | ||
37 | + "life": { | ||
38 | + "type": "integer", | ||
39 | + "format": "int64", | ||
40 | + "example": 69889 | ||
41 | + }, | ||
42 | + "packets": { | ||
43 | + "type": "integer", | ||
44 | + "format": "int64", | ||
45 | + "example": 22546 | ||
46 | + }, | ||
47 | + "bytes": { | ||
48 | + "type": "integer", | ||
49 | + "format": "int64", | ||
50 | + "example": 1826226 | ||
51 | + }, | ||
52 | + "referenceCount": { | ||
53 | + "type": "integer", | ||
54 | + "format": "int64", | ||
55 | + "example": 1826226 | ||
56 | + }, | ||
57 | + "type": { | ||
58 | + "type": "string", | ||
59 | + "example": "ALL" | ||
60 | + }, | ||
61 | + "deviceId": { | ||
62 | + "type": "string", | ||
63 | + "example": "of:0000000000000003" | ||
64 | + }, | ||
65 | + "buckets": { | ||
66 | + "type": "array", | ||
67 | + "xml": { | ||
68 | + "name": "buckets", | ||
69 | + "wrapped": true | ||
70 | + }, | ||
71 | + "items": { | ||
72 | + "type": "object", | ||
73 | + "title": "buckets", | ||
74 | + "required": [ | ||
75 | + "treatment", | ||
76 | + "weight", | ||
77 | + "watchPort", | ||
78 | + "watchGroup" | ||
79 | + ], | ||
80 | + "properties": { | ||
81 | + "treatment": { | ||
82 | + "type": "object", | ||
83 | + "title": "treatment", | ||
84 | + "required": [ | ||
85 | + "instructions", | ||
86 | + "deferred" | ||
87 | + ], | ||
88 | + "properties": { | ||
89 | + "instructions": { | ||
90 | + "type": "array", | ||
91 | + "title": "treatment", | ||
92 | + "required": [ | ||
93 | + "properties", | ||
94 | + "port" | ||
95 | + ], | ||
96 | + "items": { | ||
97 | + "type": "object", | ||
98 | + "title": "instructions", | ||
99 | + "required": [ | ||
100 | + "type", | ||
101 | + "port" | ||
102 | + ], | ||
103 | + "properties": { | ||
104 | + "type": { | ||
105 | + "type": "string", | ||
106 | + "example": "OUTPUT" | ||
107 | + }, | ||
108 | + "port": { | ||
109 | + "type": "string", | ||
110 | + "example": "2" | ||
111 | + } | ||
112 | + } | ||
113 | + } | ||
114 | + } | ||
115 | + } | ||
116 | + } | ||
117 | + } | ||
118 | + } | ||
119 | + } | ||
120 | + } | ||
121 | + } | ||
122 | + } | ||
123 | + } | ||
124 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +{ | ||
2 | + "type": "object", | ||
3 | + "title": "group", | ||
4 | + "required": [ | ||
5 | + "type", | ||
6 | + "deviceId", | ||
7 | + "appCookie", | ||
8 | + "groupId", | ||
9 | + "buckets" | ||
10 | + ], | ||
11 | + "properties": { | ||
12 | + "type": { | ||
13 | + "type": "string", | ||
14 | + "example": "ALL" | ||
15 | + }, | ||
16 | + "deviceId": { | ||
17 | + "type": "string", | ||
18 | + "example": "of:0000000000000001" | ||
19 | + }, | ||
20 | + "appCookie": { | ||
21 | + "type": "string", | ||
22 | + "example": "1" | ||
23 | + }, | ||
24 | + "groupId": { | ||
25 | + "type": "string", | ||
26 | + "example": "1" | ||
27 | + }, | ||
28 | + "buckets": { | ||
29 | + "type": "array", | ||
30 | + "xml": { | ||
31 | + "name": "buckets", | ||
32 | + "wrapped": true | ||
33 | + }, | ||
34 | + "items": { | ||
35 | + "type": "object", | ||
36 | + "title": "buckets", | ||
37 | + "required": [ | ||
38 | + "treatment", | ||
39 | + "weight", | ||
40 | + "watchPort", | ||
41 | + "watchGroup" | ||
42 | + ], | ||
43 | + "properties": { | ||
44 | + "treatment": { | ||
45 | + "type": "object", | ||
46 | + "title": "treatment", | ||
47 | + "required": [ | ||
48 | + "instructions", | ||
49 | + "deferred" | ||
50 | + ], | ||
51 | + "properties": { | ||
52 | + "instructions": { | ||
53 | + "type": "array", | ||
54 | + "title": "treatment", | ||
55 | + "required": [ | ||
56 | + "properties", | ||
57 | + "port" | ||
58 | + ], | ||
59 | + "items": { | ||
60 | + "type": "object", | ||
61 | + "title": "instructions", | ||
62 | + "required": [ | ||
63 | + "type", | ||
64 | + "port" | ||
65 | + ], | ||
66 | + "properties": { | ||
67 | + "type": { | ||
68 | + "type": "string", | ||
69 | + "example": "OUTPUT" | ||
70 | + }, | ||
71 | + "port": { | ||
72 | + "type": "string", | ||
73 | + "example": "2" | ||
74 | + } | ||
75 | + } | ||
76 | + } | ||
77 | + } | ||
78 | + } | ||
79 | + } | ||
80 | + } | ||
81 | + } | ||
82 | + } | ||
83 | + } | ||
84 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +/* | ||
2 | + * Copyright 2014-2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +package org.onosproject.rest; | ||
18 | + | ||
19 | +import com.eclipsesource.json.JsonArray; | ||
20 | +import com.eclipsesource.json.JsonObject; | ||
21 | +import com.google.common.collect.ImmutableSet; | ||
22 | +import com.sun.jersey.api.client.ClientResponse; | ||
23 | +import com.sun.jersey.api.client.WebResource; | ||
24 | +import org.hamcrest.Description; | ||
25 | +import org.hamcrest.TypeSafeMatcher; | ||
26 | +import org.junit.After; | ||
27 | +import org.junit.Before; | ||
28 | +import org.junit.Test; | ||
29 | +import org.onlab.osgi.ServiceDirectory; | ||
30 | +import org.onlab.osgi.TestServiceDirectory; | ||
31 | +import org.onlab.rest.BaseResource; | ||
32 | +import org.onosproject.codec.CodecService; | ||
33 | +import org.onosproject.codec.impl.CodecManager; | ||
34 | +import org.onosproject.codec.impl.GroupCodec; | ||
35 | +import org.onosproject.core.ApplicationId; | ||
36 | +import org.onosproject.core.CoreService; | ||
37 | +import org.onosproject.core.DefaultApplicationId; | ||
38 | +import org.onosproject.core.DefaultGroupId; | ||
39 | +import org.onosproject.core.GroupId; | ||
40 | +import org.onosproject.net.DefaultDevice; | ||
41 | +import org.onosproject.net.Device; | ||
42 | +import org.onosproject.net.DeviceId; | ||
43 | +import org.onosproject.net.NetTestTools; | ||
44 | +import org.onosproject.net.device.DeviceService; | ||
45 | +import org.onosproject.net.group.DefaultGroupKey; | ||
46 | +import org.onosproject.net.group.Group; | ||
47 | +import org.onosproject.net.group.GroupBucket; | ||
48 | +import org.onosproject.net.group.GroupBuckets; | ||
49 | +import org.onosproject.net.group.GroupDescription; | ||
50 | +import org.onosproject.net.group.GroupKey; | ||
51 | +import org.onosproject.net.group.GroupService; | ||
52 | + | ||
53 | +import javax.ws.rs.core.MediaType; | ||
54 | +import java.io.InputStream; | ||
55 | +import java.net.HttpURLConnection; | ||
56 | +import java.util.ArrayList; | ||
57 | +import java.util.HashMap; | ||
58 | +import java.util.HashSet; | ||
59 | +import java.util.List; | ||
60 | +import java.util.Set; | ||
61 | + | ||
62 | +import static org.easymock.EasyMock.anyObject; | ||
63 | +import static org.easymock.EasyMock.anyShort; | ||
64 | +import static org.easymock.EasyMock.createMock; | ||
65 | +import static org.easymock.EasyMock.expect; | ||
66 | +import static org.easymock.EasyMock.expectLastCall; | ||
67 | +import static org.easymock.EasyMock.replay; | ||
68 | +import static org.easymock.EasyMock.verify; | ||
69 | +import static org.hamcrest.Matchers.hasSize; | ||
70 | +import static org.hamcrest.Matchers.is; | ||
71 | +import static org.hamcrest.Matchers.notNullValue; | ||
72 | +import static org.junit.Assert.assertThat; | ||
73 | +import static org.onosproject.net.NetTestTools.APP_ID; | ||
74 | + | ||
75 | +/** | ||
76 | + * Unit tests for Groups REST APIs. | ||
77 | + */ | ||
78 | +public class GroupsResourceTest extends ResourceTest { | ||
79 | + final GroupService mockGroupService = createMock(GroupService.class); | ||
80 | + CoreService mockCoreService = createMock(CoreService.class); | ||
81 | + final DeviceService mockDeviceService = createMock(DeviceService.class); | ||
82 | + | ||
83 | + final HashMap<DeviceId, Set<Group>> groups = new HashMap<>(); | ||
84 | + | ||
85 | + | ||
86 | + final DeviceId deviceId1 = DeviceId.deviceId("1"); | ||
87 | + final DeviceId deviceId2 = DeviceId.deviceId("2"); | ||
88 | + final DeviceId deviceId3 = DeviceId.deviceId("3"); | ||
89 | + final Device device1 = new DefaultDevice(null, deviceId1, Device.Type.OTHER, | ||
90 | + "", "", "", "", null); | ||
91 | + final Device device2 = new DefaultDevice(null, deviceId2, Device.Type.OTHER, | ||
92 | + "", "", "", "", null); | ||
93 | + | ||
94 | + final MockGroup group1 = new MockGroup(deviceId1, 1, "111", 1); | ||
95 | + final MockGroup group2 = new MockGroup(deviceId1, 2, "222", 2); | ||
96 | + | ||
97 | + final MockGroup group3 = new MockGroup(deviceId2, 3, "333", 3); | ||
98 | + final MockGroup group4 = new MockGroup(deviceId2, 4, "444", 4); | ||
99 | + | ||
100 | + final MockGroup group5 = new MockGroup(deviceId3, 5, "555", 5); | ||
101 | + final MockGroup group6 = new MockGroup(deviceId3, 6, "666", 6); | ||
102 | + | ||
103 | + /** | ||
104 | + * Mock class for a group. | ||
105 | + */ | ||
106 | + private static class MockGroup implements Group { | ||
107 | + | ||
108 | + final DeviceId deviceId; | ||
109 | + final ApplicationId appId; | ||
110 | + final GroupKey appCookie; | ||
111 | + final long baseValue; | ||
112 | + final List<GroupBucket> bucketList; | ||
113 | + GroupBuckets buckets; | ||
114 | + | ||
115 | + public MockGroup(DeviceId deviceId, int appId, String appCookie, int id) { | ||
116 | + this.deviceId = deviceId; | ||
117 | + this.appId = new DefaultApplicationId(appId, String.valueOf(appId)); | ||
118 | + this.appCookie = new DefaultGroupKey(appCookie.getBytes()); | ||
119 | + this.baseValue = id * 100; | ||
120 | + this.bucketList = new ArrayList<>(); | ||
121 | + this.buckets = new GroupBuckets(bucketList); | ||
122 | + } | ||
123 | + | ||
124 | + @Override | ||
125 | + public GroupId id() { | ||
126 | + return new DefaultGroupId((int) baseValue + 55); | ||
127 | + } | ||
128 | + | ||
129 | + @Override | ||
130 | + public GroupState state() { | ||
131 | + return GroupState.ADDED; | ||
132 | + } | ||
133 | + | ||
134 | + @Override | ||
135 | + public long life() { | ||
136 | + return baseValue + 11; | ||
137 | + } | ||
138 | + | ||
139 | + @Override | ||
140 | + public long packets() { | ||
141 | + return baseValue + 22; | ||
142 | + } | ||
143 | + | ||
144 | + @Override | ||
145 | + public long bytes() { | ||
146 | + return baseValue + 33; | ||
147 | + } | ||
148 | + | ||
149 | + @Override | ||
150 | + public long referenceCount() { | ||
151 | + return baseValue + 44; | ||
152 | + } | ||
153 | + | ||
154 | + @Override | ||
155 | + public Type type() { | ||
156 | + return GroupDescription.Type.ALL; | ||
157 | + } | ||
158 | + | ||
159 | + @Override | ||
160 | + public DeviceId deviceId() { | ||
161 | + return this.deviceId; | ||
162 | + } | ||
163 | + | ||
164 | + @Override | ||
165 | + public ApplicationId appId() { | ||
166 | + return this.appId; | ||
167 | + } | ||
168 | + | ||
169 | + @Override | ||
170 | + public GroupKey appCookie() { | ||
171 | + return this.appCookie; | ||
172 | + } | ||
173 | + | ||
174 | + @Override | ||
175 | + public Integer givenGroupId() { | ||
176 | + return (int) baseValue + 55; | ||
177 | + } | ||
178 | + | ||
179 | + @Override | ||
180 | + public GroupBuckets buckets() { | ||
181 | + return this.buckets; | ||
182 | + } | ||
183 | + } | ||
184 | + | ||
185 | + /** | ||
186 | + * Populates some groups used as testing data. | ||
187 | + */ | ||
188 | + private void setupMockGroups() { | ||
189 | + final Set<Group> groups1 = new HashSet<>(); | ||
190 | + groups1.add(group1); | ||
191 | + groups1.add(group2); | ||
192 | + | ||
193 | + final Set<Group> groups2 = new HashSet<>(); | ||
194 | + groups2.add(group3); | ||
195 | + groups2.add(group4); | ||
196 | + | ||
197 | + groups.put(deviceId1, groups1); | ||
198 | + groups.put(deviceId2, groups2); | ||
199 | + | ||
200 | + expect(mockGroupService.getGroups(deviceId1)) | ||
201 | + .andReturn(groups.get(deviceId1)).anyTimes(); | ||
202 | + expect(mockGroupService.getGroups(deviceId2)) | ||
203 | + .andReturn(groups.get(deviceId2)).anyTimes(); | ||
204 | + } | ||
205 | + | ||
206 | + /** | ||
207 | + * Sets up the global values for all the tests. | ||
208 | + */ | ||
209 | + @Before | ||
210 | + public void setUpTest() { | ||
211 | + // Mock device service | ||
212 | + expect(mockDeviceService.getDevice(deviceId1)) | ||
213 | + .andReturn(device1); | ||
214 | + expect(mockDeviceService.getDevice(deviceId2)) | ||
215 | + .andReturn(device2); | ||
216 | + expect(mockDeviceService.getDevices()) | ||
217 | + .andReturn(ImmutableSet.of(device1, device2)); | ||
218 | + | ||
219 | + // Mock Core Service | ||
220 | + expect(mockCoreService.getAppId(anyShort())) | ||
221 | + .andReturn(NetTestTools.APP_ID).anyTimes(); | ||
222 | + expect(mockCoreService.registerApplication(GroupCodec.REST_APP_ID)) | ||
223 | + .andReturn(APP_ID).anyTimes(); | ||
224 | + replay(mockCoreService); | ||
225 | + | ||
226 | + // Register the services needed for the test | ||
227 | + final CodecManager codecService = new CodecManager(); | ||
228 | + codecService.activate(); | ||
229 | + ServiceDirectory testDirectory = | ||
230 | + new TestServiceDirectory() | ||
231 | + .add(GroupService.class, mockGroupService) | ||
232 | + .add(DeviceService.class, mockDeviceService) | ||
233 | + .add(CodecService.class, codecService) | ||
234 | + .add(CoreService.class, mockCoreService); | ||
235 | + | ||
236 | + BaseResource.setServiceDirectory(testDirectory); | ||
237 | + } | ||
238 | + | ||
239 | + /** | ||
240 | + * Cleans up and verifies the mocks. | ||
241 | + */ | ||
242 | + @After | ||
243 | + public void tearDownTest() { | ||
244 | + verify(mockGroupService); | ||
245 | + verify(mockCoreService); | ||
246 | + } | ||
247 | + | ||
248 | + /** | ||
249 | + * Hamcrest matcher to check that a group representation in JSON matches | ||
250 | + * the actual group. | ||
251 | + */ | ||
252 | + public static class GroupJsonMatcher extends TypeSafeMatcher<JsonObject> { | ||
253 | + private final Group group; | ||
254 | + private final String expectedAppId; | ||
255 | + private String reason = ""; | ||
256 | + | ||
257 | + public GroupJsonMatcher(Group groupValue, String expectedAppIdValue) { | ||
258 | + group = groupValue; | ||
259 | + expectedAppId = expectedAppIdValue; | ||
260 | + } | ||
261 | + | ||
262 | + @Override | ||
263 | + public boolean matchesSafely(JsonObject jsonGroup) { | ||
264 | + // check id | ||
265 | + final String jsonId = jsonGroup.get("id").asString(); | ||
266 | + final String groupId = group.id().toString(); | ||
267 | + if (!jsonId.equals(groupId)) { | ||
268 | + reason = "id " + group.id().toString(); | ||
269 | + return false; | ||
270 | + } | ||
271 | + | ||
272 | + // check application id | ||
273 | + final String jsonAppId = jsonGroup.get("appId").asString(); | ||
274 | + final String appId = group.appId().toString(); | ||
275 | + if (!jsonAppId.equals(appId)) { | ||
276 | + reason = "appId " + group.appId().toString(); | ||
277 | + return false; | ||
278 | + } | ||
279 | + | ||
280 | + // check device id | ||
281 | + final String jsonDeviceId = jsonGroup.get("deviceId").asString(); | ||
282 | + if (!jsonDeviceId.equals(group.deviceId().toString())) { | ||
283 | + reason = "deviceId " + group.deviceId(); | ||
284 | + return false; | ||
285 | + } | ||
286 | + | ||
287 | + // check bucket array | ||
288 | + if (group.buckets().buckets() != null) { | ||
289 | + final JsonArray jsonBuckets = jsonGroup.get("buckets").asArray(); | ||
290 | + if (group.buckets().buckets().size() != jsonBuckets.size()) { | ||
291 | + reason = "buckets array size of " + | ||
292 | + Integer.toString(group.buckets().buckets().size()); | ||
293 | + return false; | ||
294 | + } | ||
295 | + for (final GroupBucket groupBucket : group.buckets().buckets()) { | ||
296 | + boolean groupBucketFound = false; | ||
297 | + for (int groupBucketIndex = 0; groupBucketIndex < jsonBuckets.size(); groupBucketIndex++) { | ||
298 | + final String jsonType = jsonBuckets.get(groupBucketIndex).asObject().get("type").asString(); | ||
299 | + final String bucketType = groupBucket.type().name(); | ||
300 | + if (jsonType.equals(bucketType)) { | ||
301 | + groupBucketFound = true; | ||
302 | + } | ||
303 | + } | ||
304 | + if (!groupBucketFound) { | ||
305 | + reason = "group bucket " + groupBucket.toString(); | ||
306 | + return false; | ||
307 | + } | ||
308 | + } | ||
309 | + } | ||
310 | + | ||
311 | + return true; | ||
312 | + } | ||
313 | + | ||
314 | + @Override | ||
315 | + public void describeTo(Description description) { | ||
316 | + description.appendText(reason); | ||
317 | + } | ||
318 | + } | ||
319 | + | ||
320 | + /** | ||
321 | + * Factory to allocate a group matcher. | ||
322 | + * | ||
323 | + * @param group group object we are looking for | ||
324 | + * @return matcher | ||
325 | + */ | ||
326 | + private static GroupJsonMatcher matchesGroup(Group group, String expectedAppName) { | ||
327 | + return new GroupJsonMatcher(group, expectedAppName); | ||
328 | + } | ||
329 | + | ||
330 | + /** | ||
331 | + * Hamcrest matcher to check that a group is represented properly in a JSON | ||
332 | + * array of flows. | ||
333 | + */ | ||
334 | + public static class GroupJsonArrayMatcher extends TypeSafeMatcher<JsonArray> { | ||
335 | + private final Group group; | ||
336 | + private String reason = ""; | ||
337 | + | ||
338 | + public GroupJsonArrayMatcher(Group groupValue) { | ||
339 | + group = groupValue; | ||
340 | + } | ||
341 | + | ||
342 | + @Override | ||
343 | + public boolean matchesSafely(JsonArray json) { | ||
344 | + boolean groupFound = false; | ||
345 | + for (int jsonGroupIndex = 0; jsonGroupIndex < json.size(); | ||
346 | + jsonGroupIndex++) { | ||
347 | + | ||
348 | + final JsonObject jsonGroup = json.get(jsonGroupIndex).asObject(); | ||
349 | + | ||
350 | + final String groupId = group.id().toString(); | ||
351 | + final String jsonGroupId = jsonGroup.get("id").asString(); | ||
352 | + if (jsonGroupId.equals(groupId)) { | ||
353 | + groupFound = true; | ||
354 | + | ||
355 | + // We found the correct group, check attribute values | ||
356 | + assertThat(jsonGroup, matchesGroup(group, APP_ID.name())); | ||
357 | + } | ||
358 | + } | ||
359 | + if (!groupFound) { | ||
360 | + reason = "Group with id " + group.id().toString() + " not found"; | ||
361 | + return false; | ||
362 | + } else { | ||
363 | + return true; | ||
364 | + } | ||
365 | + } | ||
366 | + | ||
367 | + @Override | ||
368 | + public void describeTo(Description description) { | ||
369 | + description.appendText(reason); | ||
370 | + } | ||
371 | + } | ||
372 | + | ||
373 | + /** | ||
374 | + * Factory to allocate a group array matcher. | ||
375 | + * | ||
376 | + * @param group group object we are looking for | ||
377 | + * @return matcher | ||
378 | + */ | ||
379 | + private static GroupJsonArrayMatcher hasGroup(Group group) { | ||
380 | + return new GroupJsonArrayMatcher(group); | ||
381 | + } | ||
382 | + | ||
383 | + /** | ||
384 | + * Tests the result of the rest api GET when there are no groups. | ||
385 | + */ | ||
386 | + @Test | ||
387 | + public void testGroupsEmptyArray() { | ||
388 | + expect(mockGroupService.getGroups(deviceId1)).andReturn(null).anyTimes(); | ||
389 | + expect(mockGroupService.getGroups(deviceId2)).andReturn(null).anyTimes(); | ||
390 | + replay(mockGroupService); | ||
391 | + replay(mockDeviceService); | ||
392 | + final WebResource rs = resource(); | ||
393 | + final String response = rs.path("groups").get(String.class); | ||
394 | + assertThat(response, is("{\"groups\":[]}")); | ||
395 | + } | ||
396 | + | ||
397 | + /** | ||
398 | + * Tests the result of the rest api GET when there are active groups. | ||
399 | + */ | ||
400 | + @Test | ||
401 | + public void testGroupsPopulatedArray() { | ||
402 | + setupMockGroups(); | ||
403 | + replay(mockGroupService); | ||
404 | + replay(mockDeviceService); | ||
405 | + final WebResource rs = resource(); | ||
406 | + final String response = rs.path("groups").get(String.class); | ||
407 | + final JsonObject result = JsonObject.readFrom(response); | ||
408 | + assertThat(result, notNullValue()); | ||
409 | + | ||
410 | + assertThat(result.names(), hasSize(1)); | ||
411 | + assertThat(result.names().get(0), is("groups")); | ||
412 | + final JsonArray jsonGroups = result.get("groups").asArray(); | ||
413 | + assertThat(jsonGroups, notNullValue()); | ||
414 | + assertThat(jsonGroups, hasGroup(group1)); | ||
415 | + assertThat(jsonGroups, hasGroup(group2)); | ||
416 | + assertThat(jsonGroups, hasGroup(group3)); | ||
417 | + assertThat(jsonGroups, hasGroup(group4)); | ||
418 | + } | ||
419 | + | ||
420 | + /** | ||
421 | + * Tests the result of a rest api GET for a device. | ||
422 | + */ | ||
423 | + @Test | ||
424 | + public void testGroupsSingleDevice() { | ||
425 | + setupMockGroups(); | ||
426 | + final Set<Group> groups = new HashSet<>(); | ||
427 | + groups.add(group5); | ||
428 | + groups.add(group6); | ||
429 | + expect(mockGroupService.getGroups(anyObject())) | ||
430 | + .andReturn(groups).anyTimes(); | ||
431 | + replay(mockGroupService); | ||
432 | + replay(mockDeviceService); | ||
433 | + final WebResource rs = resource(); | ||
434 | + final String response = rs.path("groups/" + deviceId3).get(String.class); | ||
435 | + final JsonObject result = JsonObject.readFrom(response); | ||
436 | + assertThat(result, notNullValue()); | ||
437 | + | ||
438 | + assertThat(result.names(), hasSize(1)); | ||
439 | + assertThat(result.names().get(0), is("groups")); | ||
440 | + final JsonArray jsonFlows = result.get("groups").asArray(); | ||
441 | + assertThat(jsonFlows, notNullValue()); | ||
442 | + assertThat(jsonFlows, hasGroup(group5)); | ||
443 | + assertThat(jsonFlows, hasGroup(group6)); | ||
444 | + } | ||
445 | + | ||
446 | + /** | ||
447 | + * Tests creating a group with POST. | ||
448 | + */ | ||
449 | + @Test | ||
450 | + public void testPost() { | ||
451 | + mockGroupService.addGroup(anyObject()); | ||
452 | + expectLastCall(); | ||
453 | + replay(mockGroupService); | ||
454 | + | ||
455 | + WebResource rs = resource(); | ||
456 | + InputStream jsonStream = GroupsResourceTest.class | ||
457 | + .getResourceAsStream("post-group.json"); | ||
458 | + | ||
459 | + ClientResponse response = rs.path("groups/of:0000000000000001") | ||
460 | + .type(MediaType.APPLICATION_JSON_TYPE) | ||
461 | + .post(ClientResponse.class, jsonStream); | ||
462 | + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED)); | ||
463 | + } | ||
464 | + | ||
465 | + /** | ||
466 | + * Tests deleting a group. | ||
467 | + */ | ||
468 | + @Test | ||
469 | + public void testDelete() { | ||
470 | + setupMockGroups(); | ||
471 | + mockGroupService.removeGroup(anyObject(), anyObject(), anyObject()); | ||
472 | + expectLastCall(); | ||
473 | + replay(mockGroupService); | ||
474 | + | ||
475 | + WebResource rs = resource(); | ||
476 | + | ||
477 | + String location = "/groups/1/111"; | ||
478 | + | ||
479 | + ClientResponse deleteResponse = rs.path(location) | ||
480 | + .type(MediaType.APPLICATION_JSON_TYPE) | ||
481 | + .delete(ClientResponse.class); | ||
482 | + assertThat(deleteResponse.getStatus(), | ||
483 | + is(HttpURLConnection.HTTP_NO_CONTENT)); | ||
484 | + } | ||
485 | +} |
1 | +{ | ||
2 | + "type": "ALL", | ||
3 | + "deviceId": "of:0000000000000001", | ||
4 | + "appCookie": "1", | ||
5 | + "groupId": "1", | ||
6 | + "buckets": [ | ||
7 | + { | ||
8 | + "treatment": { | ||
9 | + "instructions": [ | ||
10 | + { | ||
11 | + "type": "OUTPUT", | ||
12 | + "port": 2 | ||
13 | + } | ||
14 | + ] | ||
15 | + } | ||
16 | + } | ||
17 | + ] | ||
18 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment