Jian Li
Committed by Gerrit Code Review

[ONOS-4016] Implement Region administration REST API

- Impelent Region management REST API
- Add unit test for Region management REST API
- Add swagger docs for Region management REST API
- Add SCR Component and Service annotation for RegionManager

Change-Id: I042e92ed7144d596659b779a59239afba832ca62
...@@ -17,9 +17,11 @@ ...@@ -17,9 +17,11 @@
17 package org.onosproject.net.region.impl; 17 package org.onosproject.net.region.impl;
18 18
19 import org.apache.felix.scr.annotations.Activate; 19 import org.apache.felix.scr.annotations.Activate;
20 +import org.apache.felix.scr.annotations.Component;
20 import org.apache.felix.scr.annotations.Deactivate; 21 import org.apache.felix.scr.annotations.Deactivate;
21 import org.apache.felix.scr.annotations.Reference; 22 import org.apache.felix.scr.annotations.Reference;
22 import org.apache.felix.scr.annotations.ReferenceCardinality; 23 import org.apache.felix.scr.annotations.ReferenceCardinality;
24 +import org.apache.felix.scr.annotations.Service;
23 import org.onosproject.cluster.NodeId; 25 import org.onosproject.cluster.NodeId;
24 import org.onosproject.event.AbstractListenerManager; 26 import org.onosproject.event.AbstractListenerManager;
25 import org.onosproject.net.DeviceId; 27 import org.onosproject.net.DeviceId;
...@@ -45,6 +47,8 @@ import static org.slf4j.LoggerFactory.getLogger; ...@@ -45,6 +47,8 @@ import static org.slf4j.LoggerFactory.getLogger;
45 /** 47 /**
46 * Provides implementation of the region service APIs. 48 * Provides implementation of the region service APIs.
47 */ 49 */
50 +@Component(immediate = true)
51 +@Service
48 public class RegionManager extends AbstractListenerManager<RegionEvent, RegionListener> 52 public class RegionManager extends AbstractListenerManager<RegionEvent, RegionListener>
49 implements RegionAdminService, RegionService { 53 implements RegionAdminService, RegionService {
50 54
......
...@@ -46,7 +46,8 @@ public class CoreWebApplication extends AbstractWebApplication { ...@@ -46,7 +46,8 @@ public class CoreWebApplication extends AbstractWebApplication {
46 MetricsWebResource.class, 46 MetricsWebResource.class,
47 FlowObjectiveWebResource.class, 47 FlowObjectiveWebResource.class,
48 MulticastRouteWebResource.class, 48 MulticastRouteWebResource.class,
49 - DeviceKeyWebResource.class 49 + DeviceKeyWebResource.class,
50 + RegionsWebResource.class
50 ); 51 );
51 } 52 }
52 } 53 }
......
1 +/*
2 + * Copyright 2016 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 com.google.common.collect.Sets;
22 +import org.onosproject.net.DeviceId;
23 +import org.onosproject.net.region.Region;
24 +import org.onosproject.net.region.RegionAdminService;
25 +import org.onosproject.net.region.RegionId;
26 +import org.onosproject.net.region.RegionService;
27 +import org.onosproject.rest.AbstractWebResource;
28 +
29 +import javax.ws.rs.Consumes;
30 +import javax.ws.rs.DELETE;
31 +import javax.ws.rs.GET;
32 +import javax.ws.rs.POST;
33 +import javax.ws.rs.PUT;
34 +import javax.ws.rs.Path;
35 +import javax.ws.rs.PathParam;
36 +import javax.ws.rs.Produces;
37 +import javax.ws.rs.core.MediaType;
38 +import javax.ws.rs.core.Response;
39 +import java.io.IOException;
40 +import java.io.InputStream;
41 +import java.net.URI;
42 +import java.net.URISyntaxException;
43 +import java.util.Set;
44 +
45 +import static org.onlab.util.Tools.nullIsNotFound;
46 +
47 +/**
48 + * Manages region and device membership.
49 + */
50 +@Path("regions")
51 +public class RegionsWebResource extends AbstractWebResource {
52 + private final RegionService regionService = get(RegionService.class);
53 + private final RegionAdminService regionAdminService = get(RegionAdminService.class);
54 +
55 + private static final String REGION_NOT_FOUND = "Region is not found for ";
56 + private static final String REGION_INVALID = "Invalid regionId in region update request";
57 + private static final String DEVICE_IDS_INVALID = "Invalid device identifiers";
58 +
59 + /**
60 + * Returns set of all regions.
61 + *
62 + * @return 200 OK
63 + * @onos.rsModel Regions
64 + */
65 + @GET
66 + @Produces(MediaType.APPLICATION_JSON)
67 + public Response getRegions() {
68 + final Iterable<Region> regions = regionService.getRegions();
69 + return ok(encodeArray(Region.class, "regions", regions)).build();
70 + }
71 +
72 + /**
73 + * Returns the region with the specified identifier.
74 + *
75 + * @param regionId region identifier
76 + * @return 200 OK, 404 not found
77 + * @onos.rsModel Region
78 + */
79 + @GET
80 + @Produces(MediaType.APPLICATION_JSON)
81 + @Path("{regionId}")
82 + public Response getRegionById(@PathParam("regionId") String regionId) {
83 + final RegionId rid = RegionId.regionId(regionId);
84 + final Region region = nullIsNotFound(regionService.getRegion(rid),
85 + REGION_NOT_FOUND + rid.toString());
86 + return ok(codec(Region.class).encode(region, this)).build();
87 + }
88 +
89 + /**
90 + * Returns the set of devices that belong to the specified region.
91 + *
92 + * @param regionId region identifier
93 + * @return 200 OK
94 + * @onos.rsModel RegionDeviceIds
95 + */
96 + @GET
97 + @Produces(MediaType.APPLICATION_JSON)
98 + @Path("{regionId}/devices")
99 + public Response getRegionDevices(@PathParam("regionId") String regionId) {
100 + final RegionId rid = RegionId.regionId(regionId);
101 + final Iterable<DeviceId> deviceIds = regionService.getRegionDevices(rid);
102 + final ObjectNode root = mapper().createObjectNode();
103 + final ArrayNode deviceIdsNode = root.putArray("deviceIds");
104 + deviceIds.forEach(did -> deviceIdsNode.add(did.toString()));
105 + return ok(root).build();
106 + }
107 +
108 + /**
109 + * Creates a new region using the supplied JSON input stream.
110 + *
111 + * @param stream region JSON stream
112 + * @return status of the request - CREATED if the JSON is correct,
113 + * BAD_REQUEST if the JSON is invalid
114 + * @onos.rsModel RegionPost
115 + */
116 + @POST
117 + @Consumes(MediaType.APPLICATION_JSON)
118 + @Produces(MediaType.APPLICATION_JSON)
119 + public Response createRegion(InputStream stream) {
120 + URI location;
121 + try {
122 + ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
123 + final Region region = codec(Region.class).decode(jsonTree, this);
124 + final Region resultRegion = regionAdminService.createRegion(region.id(),
125 + region.name(), region.type(), region.masters());
126 + location = new URI(resultRegion.id().id());
127 + } catch (IOException | URISyntaxException e) {
128 + throw new IllegalArgumentException(e);
129 + }
130 +
131 + return Response.created(location).build();
132 + }
133 +
134 + /**
135 + * Updates the specified region using the supplied JSON input stream.
136 + *
137 + * @param regionId region identifier
138 + * @param stream region JSON stream
139 + * @return status of the request - UPDATED if the JSON is correct,
140 + * BAD_REQUEST if the JSON is invalid
141 + * @onos.rsModel RegionPost
142 + */
143 + @PUT
144 + @Path("{regionId}")
145 + @Consumes(MediaType.APPLICATION_JSON)
146 + @Produces(MediaType.APPLICATION_JSON)
147 + public Response updateRegion(@PathParam("regionId") String regionId,
148 + InputStream stream) {
149 + try {
150 + ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
151 + JsonNode specifiedRegionId = jsonTree.get("id");
152 +
153 + if (specifiedRegionId != null &&
154 + !specifiedRegionId.asText().equals(regionId)) {
155 + throw new IllegalArgumentException(REGION_INVALID);
156 + }
157 +
158 + final Region region = codec(Region.class).decode(jsonTree, this);
159 + regionAdminService.updateRegion(region.id(),
160 + region.name(), region.type(), region.masters());
161 + } catch (IOException e) {
162 + throw new IllegalArgumentException(e);
163 + }
164 +
165 + return Response.ok().build();
166 + }
167 +
168 + /**
169 + * Removes the specified region using the given region identifier.
170 + *
171 + * @param regionId region identifier
172 + * @return 200 OK, 404 not found
173 + */
174 + @DELETE
175 + @Path("{regionId}")
176 + @Produces(MediaType.APPLICATION_JSON)
177 + public Response removeRegion(@PathParam("regionId") String regionId) {
178 + final RegionId rid = RegionId.regionId(regionId);
179 + regionAdminService.removeRegion(rid);
180 + return Response.ok().build();
181 + }
182 +
183 + /**
184 + * Adds the specified collection of devices to the region.
185 + *
186 + * @param regionId region identifier
187 + * @param stream deviceIds JSON stream
188 + * @return status of the request - CREATED if the JSON is correct,
189 + * BAD_REQUEST if the JSON is invalid
190 + * @onos.rsModel RegionDeviceIds
191 + */
192 + @POST
193 + @Path("{regionId}/devices")
194 + @Consumes(MediaType.APPLICATION_JSON)
195 + @Produces(MediaType.APPLICATION_JSON)
196 + public Response addDevices(@PathParam("regionId") String regionId,
197 + InputStream stream) {
198 + final RegionId rid = RegionId.regionId(regionId);
199 +
200 + URI location;
201 + try {
202 + regionAdminService.addDevices(rid, extractDeviceIds(stream));
203 + location = new URI(rid.id());
204 + } catch (IOException | URISyntaxException e) {
205 + throw new IllegalArgumentException(e);
206 + }
207 +
208 + return Response.created(location).build();
209 + }
210 +
211 + /**
212 + * Removes the specified collection of devices from the region.
213 + *
214 + * @param regionId region identifier
215 + * @param stream deviceIds JSON stream
216 + * @return 200 OK, 404 not found
217 + * @onos.rsModel RegionDeviceIds
218 + */
219 + @DELETE
220 + @Path("{regionId}/devices")
221 + @Consumes(MediaType.APPLICATION_JSON)
222 + @Produces(MediaType.APPLICATION_JSON)
223 + public Response removeDevices(@PathParam("regionId") String regionId,
224 + InputStream stream) {
225 + final RegionId rid = RegionId.regionId(regionId);
226 +
227 + try {
228 + regionAdminService.removeDevices(rid, extractDeviceIds(stream));
229 + } catch (IOException e) {
230 + throw new IllegalArgumentException(e);
231 + }
232 +
233 + return Response.ok().build();
234 + }
235 +
236 + /**
237 + * Extracts device ids from a given JSON string.
238 + *
239 + * @param stream deviceIds JSON stream
240 + * @return a set of device identifiers
241 + * @throws IOException
242 + */
243 + private Set<DeviceId> extractDeviceIds(InputStream stream) throws IOException {
244 + ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
245 + JsonNode deviceIdsJson = jsonTree.get("deviceIds");
246 +
247 + if (deviceIdsJson == null || deviceIdsJson.size() == 0) {
248 + throw new IllegalArgumentException(DEVICE_IDS_INVALID);
249 + }
250 +
251 + Set<DeviceId> deviceIds = Sets.newHashSet();
252 + deviceIdsJson.forEach(did -> deviceIds.add(DeviceId.deviceId(did.asText())));
253 +
254 + return deviceIds;
255 + }
256 +}
1 +{
2 + "type": "object",
3 + "title": "region",
4 + "required": [
5 + "id",
6 + "name",
7 + "type",
8 + "masters"
9 + ],
10 + "properties": {
11 + "id": {
12 + "type": "string",
13 + "example": "1"
14 + },
15 + "name": {
16 + "type": "string",
17 + "example": "region"
18 + },
19 + "type": {
20 + "type": "string",
21 + "example": "ROOM"
22 + },
23 + "masters": {
24 + "type": "array",
25 + "xml": {
26 + "name": "masters",
27 + "wrapped": true
28 + },
29 + "items": {
30 + "type": "array",
31 + "xml": {
32 + "name": "masters",
33 + "wrapped": true
34 + },
35 + "items": {
36 + "type": "string",
37 + "example": "1"
38 + }
39 + }
40 + }
41 + }
42 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "deviceIds",
4 + "required": [
5 + "deviceIds"
6 + ],
7 + "properties": {
8 + "deviceIds": {
9 + "type": "array",
10 + "xml": {
11 + "name": "deviceIds",
12 + "wrapped": true
13 + },
14 + "items": {
15 + "type": "string",
16 + "example": "of:0000000000000001"
17 + }
18 + }
19 + }
20 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "region",
4 + "required": [
5 + "id",
6 + "name",
7 + "type",
8 + "masters"
9 + ],
10 + "properties": {
11 + "id": {
12 + "type": "string",
13 + "example": "1"
14 + },
15 + "name": {
16 + "type": "string",
17 + "example": "region"
18 + },
19 + "type": {
20 + "type": "string",
21 + "example": "ROOM"
22 + },
23 + "masters": {
24 + "type": "array",
25 + "xml": {
26 + "name": "masters",
27 + "wrapped": true
28 + },
29 + "items": {
30 + "type": "array",
31 + "xml": {
32 + "name": "masters",
33 + "wrapped": true
34 + },
35 + "items": {
36 + "type": "string",
37 + "example": "1"
38 + }
39 + }
40 + }
41 + }
42 +}
1 +{
2 + "type": "object",
3 + "title": "regions",
4 + "required": [
5 + "regions"
6 + ],
7 + "properties": {
8 + "regions": {
9 + "type": "array",
10 + "xml": {
11 + "name": "regions",
12 + "wrapped": true
13 + },
14 + "items": {
15 + "type": "object",
16 + "title": "region",
17 + "required": [
18 + "id",
19 + "name",
20 + "type",
21 + "masters"
22 + ],
23 + "properties": {
24 + "id": {
25 + "type": "string",
26 + "example": "1"
27 + },
28 + "name": {
29 + "type": "string",
30 + "example": "region"
31 + },
32 + "type": {
33 + "type": "string",
34 + "example": "ROOM"
35 + },
36 + "masters": {
37 + "type": "array",
38 + "xml": {
39 + "name": "masters",
40 + "wrapped": true
41 + },
42 + "items": {
43 + "type": "array",
44 + "xml": {
45 + "name": "masters",
46 + "wrapped": true
47 + },
48 + "items": {
49 + "type": "string",
50 + "example": "1"
51 + }
52 + }
53 + }
54 + }
55 + }
56 + }
57 + }
58 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016 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;
17 +
18 +import com.eclipsesource.json.Json;
19 +import com.eclipsesource.json.JsonArray;
20 +import com.eclipsesource.json.JsonObject;
21 +import com.google.common.collect.ImmutableList;
22 +import com.google.common.collect.ImmutableSet;
23 +import com.google.common.collect.Sets;
24 +import com.sun.jersey.api.client.ClientResponse;
25 +import com.sun.jersey.api.client.WebResource;
26 +import org.hamcrest.Description;
27 +import org.hamcrest.TypeSafeMatcher;
28 +import org.junit.Before;
29 +import org.junit.Test;
30 +import org.onlab.osgi.ServiceDirectory;
31 +import org.onlab.osgi.TestServiceDirectory;
32 +import org.onlab.rest.BaseResource;
33 +import org.onosproject.cluster.NodeId;
34 +import org.onosproject.codec.CodecService;
35 +import org.onosproject.codec.impl.CodecManager;
36 +import org.onosproject.net.DeviceId;
37 +import org.onosproject.net.region.Region;
38 +import org.onosproject.net.region.RegionAdminService;
39 +import org.onosproject.net.region.RegionId;
40 +import org.onosproject.net.region.RegionService;
41 +import org.onosproject.rest.resources.CoreWebApplication;
42 +
43 +import javax.ws.rs.core.MediaType;
44 +import java.io.InputStream;
45 +import java.net.HttpURLConnection;
46 +import java.util.List;
47 +import java.util.Set;
48 +
49 +import static org.easymock.EasyMock.anyObject;
50 +import static org.easymock.EasyMock.createMock;
51 +import static org.easymock.EasyMock.expect;
52 +import static org.easymock.EasyMock.expectLastCall;
53 +import static org.easymock.EasyMock.replay;
54 +import static org.easymock.EasyMock.verify;
55 +import static org.hamcrest.Matchers.hasSize;
56 +import static org.hamcrest.Matchers.is;
57 +import static org.hamcrest.Matchers.notNullValue;
58 +import static org.junit.Assert.assertThat;
59 +
60 +/**
61 + * Unit tests for region REST APIs.
62 + */
63 +public class RegionsResourceTest extends ResourceTest {
64 +
65 + final RegionService mockRegionService = createMock(RegionService.class);
66 + final RegionAdminService mockRegionAdminService = createMock(RegionAdminService.class);
67 +
68 + final RegionId regionId1 = RegionId.regionId("1");
69 + final RegionId regionId2 = RegionId.regionId("2");
70 + final RegionId regionId3 = RegionId.regionId("3");
71 +
72 + final MockRegion region1 = new MockRegion(regionId1, "r1", Region.Type.RACK);
73 + final MockRegion region2 = new MockRegion(regionId2, "r2", Region.Type.ROOM);
74 + final MockRegion region3 = new MockRegion(regionId3, "r3", Region.Type.CAMPUS);
75 +
76 + public RegionsResourceTest() {
77 + super(CoreWebApplication.class);
78 + }
79 +
80 + /**
81 + * Mock class for a region.
82 + */
83 + private static class MockRegion implements Region {
84 +
85 + private final RegionId id;
86 + private final String name;
87 + private final Type type;
88 + private final List<Set<NodeId>> masters;
89 +
90 + public MockRegion(RegionId id, String name, Type type) {
91 + this.id = id;
92 + this.name = name;
93 + this.type = type;
94 +
95 + final NodeId nodeId1 = NodeId.nodeId("1");
96 + final NodeId nodeId2 = NodeId.nodeId("2");
97 + final NodeId nodeId3 = NodeId.nodeId("3");
98 + final NodeId nodeId4 = NodeId.nodeId("4");
99 +
100 + Set<NodeId> nodeIds1 = ImmutableSet.of(nodeId1);
101 + Set<NodeId> nodeIds2 = ImmutableSet.of(nodeId1, nodeId2);
102 + Set<NodeId> nodeIds3 = ImmutableSet.of(nodeId1, nodeId2, nodeId3);
103 + Set<NodeId> nodeIds4 = ImmutableSet.of(nodeId1, nodeId2, nodeId3, nodeId4);
104 +
105 + this.masters = ImmutableList.of(nodeIds1, nodeIds2, nodeIds3, nodeIds4);
106 + }
107 +
108 + @Override
109 + public RegionId id() {
110 + return this.id;
111 + }
112 +
113 + @Override
114 + public String name() {
115 + return this.name;
116 + }
117 +
118 + @Override
119 + public Type type() {
120 + return this.type;
121 + }
122 +
123 + @Override
124 + public List<Set<NodeId>> masters() {
125 + return this.masters;
126 + }
127 + }
128 +
129 + /**
130 + * Sets up the global values for all the tests.
131 + */
132 + @Before
133 + public void setupTest() {
134 + final CodecManager codecService = new CodecManager();
135 + codecService.activate();
136 + ServiceDirectory testDirectory =
137 + new TestServiceDirectory()
138 + .add(RegionService.class, mockRegionService)
139 + .add(RegionAdminService.class, mockRegionAdminService)
140 + .add(CodecService.class, codecService);
141 + BaseResource.setServiceDirectory(testDirectory);
142 + }
143 +
144 + /**
145 + * Hamcrest matcher to check that a meter representation in JSON matches
146 + * the actual meter.
147 + */
148 + public static class RegionJsonMatcher extends TypeSafeMatcher<JsonObject> {
149 + private final Region region;
150 + private String reason = "";
151 +
152 + public RegionJsonMatcher(Region regionValue) {
153 + this.region = regionValue;
154 + }
155 +
156 + @Override
157 + protected boolean matchesSafely(JsonObject jsonRegion) {
158 +
159 + // check id
160 + String jsonRegionId = jsonRegion.get("id").asString();
161 + String regionId = region.id().toString();
162 + if (!jsonRegionId.equals(regionId)) {
163 + reason = "region id was " + jsonRegionId;
164 + return false;
165 + }
166 +
167 + // check type
168 + String jsonType = jsonRegion.get("type").asString();
169 + String type = region.type().toString();
170 + if (!jsonType.equals(type)) {
171 + reason = "type was " + jsonType;
172 + return false;
173 + }
174 +
175 + // check name
176 + String jsonName = jsonRegion.get("name").asString();
177 + String name = region.name();
178 + if (!jsonName.equals(name)) {
179 + reason = "name was " + jsonName;
180 + return false;
181 + }
182 +
183 + // check size of master array
184 + JsonArray jsonMasters = jsonRegion.get("masters").asArray();
185 + if (jsonMasters.size() != region.masters().size()) {
186 + reason = "masters size was " + jsonMasters.size();
187 + return false;
188 + }
189 +
190 + // check master
191 + for (Set<NodeId> set : region.masters()) {
192 + boolean masterFound = false;
193 + for (int masterIndex = 0; masterIndex < jsonMasters.size(); masterIndex++) {
194 + masterFound = checkEquality(jsonMasters.get(masterIndex).asArray(), set);
195 + }
196 +
197 + if (!masterFound) {
198 + reason = "master not found " + set.toString();
199 + return false;
200 + }
201 + }
202 +
203 + return true;
204 + }
205 +
206 + @Override
207 + public void describeTo(Description description) {
208 + description.appendText(reason);
209 + }
210 +
211 + private Set<NodeId> jsonToSet(JsonArray nodes) {
212 + final Set<NodeId> nodeIds = Sets.newHashSet();
213 + nodes.forEach(node -> nodeIds.add(NodeId.nodeId(node.asString())));
214 + return nodeIds;
215 + }
216 +
217 + private boolean checkEquality(JsonArray nodes, Set<NodeId> nodeIds) {
218 + Set<NodeId> jsonSet = jsonToSet(nodes);
219 + if (jsonSet.size() == nodes.size()) {
220 + return jsonSet.containsAll(nodeIds);
221 + }
222 + return false;
223 + }
224 + }
225 +
226 + private static RegionJsonMatcher matchesRegion(Region region) {
227 + return new RegionJsonMatcher(region);
228 + }
229 +
230 + /**
231 + * Hamcrest matcher to check that a region is represented properly in a JSON
232 + * array of regions.
233 + */
234 + public static class RegionJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
235 + private final Region region;
236 + private String reason = "";
237 +
238 + public RegionJsonArrayMatcher(Region regionValue) {
239 + this.region = regionValue;
240 + }
241 +
242 + @Override
243 + protected boolean matchesSafely(JsonArray json) {
244 + boolean regionFound = false;
245 + for (int jsonRegionIndex = 0; jsonRegionIndex < json.size(); jsonRegionIndex++) {
246 + final JsonObject jsonRegion = json.get(jsonRegionIndex).asObject();
247 +
248 + final String regionId = region.id().toString();
249 + final String jsonRegionId = jsonRegion.get("id").asString();
250 + if (jsonRegionId.equals(regionId)) {
251 + regionFound = true;
252 + assertThat(jsonRegion, matchesRegion(region));
253 + }
254 + }
255 +
256 + if (!regionFound) {
257 + reason = "Region with id " + region.id().toString() + " not found";
258 + return false;
259 + } else {
260 + return true;
261 + }
262 + }
263 +
264 + @Override
265 + public void describeTo(Description description) {
266 + description.appendText(reason);
267 + }
268 + }
269 +
270 + /**
271 + * Factory to allocate a region array matcher.
272 + *
273 + * @param region region object we are looking for
274 + * @return matcher
275 + */
276 + private static RegionJsonArrayMatcher hasRegion(Region region) {
277 + return new RegionJsonArrayMatcher(region);
278 + }
279 +
280 + @Test
281 + public void testRegionEmptyArray() {
282 + expect(mockRegionService.getRegions()).andReturn(ImmutableSet.of()).anyTimes();
283 + replay((mockRegionService));
284 + final WebResource rs = resource();
285 + final String response = rs.path("regions").get(String.class);
286 + assertThat(response, is("{\"regions\":[]}"));
287 +
288 + verify(mockRegionService);
289 + }
290 +
291 + /**
292 + * Tests the results of the REST API GET when there are active regions.
293 + */
294 + @Test
295 + public void testRegionsPopulatedArray() {
296 + final Set<Region> regions = ImmutableSet.of(region1, region2, region3);
297 + expect(mockRegionService.getRegions()).andReturn(regions).anyTimes();
298 + replay(mockRegionService);
299 +
300 + final WebResource rs = resource();
301 + final String response = rs.path("regions").get(String.class);
302 + final JsonObject result = Json.parse(response).asObject();
303 + assertThat(result, notNullValue());
304 +
305 + assertThat(result.names(), hasSize(1));
306 + assertThat(result.names().get(0), is("regions"));
307 + final JsonArray jsonRegions = result.get("regions").asArray();
308 + assertThat(jsonRegions, notNullValue());
309 + assertThat(jsonRegions, hasRegion(region1));
310 + assertThat(jsonRegions, hasRegion(region2));
311 + assertThat(jsonRegions, hasRegion(region3));
312 +
313 + verify(mockRegionService);
314 +
315 + }
316 +
317 + /**
318 + * Tests the result of a REST API GET for a region with region id.
319 + */
320 + @Test
321 + public void testGetRegionById() {
322 + expect(mockRegionService.getRegion(anyObject())).andReturn(region1).anyTimes();
323 + replay(mockRegionService);
324 +
325 + final WebResource rs = resource();
326 + final String response = rs.path("regions/" + regionId1.toString()).get(String.class);
327 + final JsonObject result = Json.parse(response).asObject();
328 + assertThat(result, notNullValue());
329 + assertThat(result, matchesRegion(region1));
330 +
331 + verify(mockRegionService);
332 + }
333 +
334 + /**
335 + * Tests creating a region with POST.
336 + */
337 + @Test
338 + public void testRegionPost() {
339 + mockRegionAdminService.createRegion(anyObject(), anyObject(),
340 + anyObject(), anyObject());
341 + expectLastCall().andReturn(region2).anyTimes();
342 + replay(mockRegionAdminService);
343 +
344 + WebResource rs = resource();
345 + InputStream jsonStream = MetersResourceTest.class
346 + .getResourceAsStream("post-region.json");
347 +
348 + ClientResponse response = rs.path("regions")
349 + .type(MediaType.APPLICATION_JSON_TYPE)
350 + .post(ClientResponse.class, jsonStream);
351 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
352 +
353 + verify(mockRegionAdminService);
354 + }
355 +
356 + /**
357 + * Tests updating a region with PUT.
358 + */
359 + @Test
360 + public void testRegionPut() {
361 + mockRegionAdminService.updateRegion(anyObject(), anyObject(),
362 + anyObject(), anyObject());
363 + expectLastCall().andReturn(region1).anyTimes();
364 + replay(mockRegionAdminService);
365 +
366 + WebResource rs = resource();
367 + InputStream jsonStream = MetersResourceTest.class
368 + .getResourceAsStream("post-region.json");
369 +
370 + ClientResponse response = rs.path("regions/" + region1.id().toString())
371 + .type(MediaType.APPLICATION_JSON_TYPE)
372 + .put(ClientResponse.class, jsonStream);
373 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
374 +
375 + verify(mockRegionAdminService);
376 + }
377 +
378 + /**
379 + * Tests deleting a region with DELETE.
380 + */
381 + @Test
382 + public void testRegionDelete() {
383 + mockRegionAdminService.removeRegion(anyObject());
384 + expectLastCall();
385 + replay(mockRegionAdminService);
386 +
387 + WebResource rs = resource();
388 + ClientResponse response = rs.path("regions/" + region1.id().toString())
389 + .delete(ClientResponse.class);
390 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
391 +
392 + verify(mockRegionAdminService);
393 + }
394 +
395 + /**
396 + * Tests retrieving device ids that are associated with the given region.
397 + */
398 + @Test
399 + public void testGetRegionDevices() {
400 + final DeviceId deviceId1 = DeviceId.deviceId("1");
401 + final DeviceId deviceId2 = DeviceId.deviceId("2");
402 + final DeviceId deviceId3 = DeviceId.deviceId("3");
403 +
404 + final Set<DeviceId> deviceIds = ImmutableSet.of(deviceId1, deviceId2, deviceId3);
405 +
406 + expect(mockRegionService.getRegionDevices(anyObject()))
407 + .andReturn(deviceIds).anyTimes();
408 + replay(mockRegionService);
409 +
410 + final WebResource rs = resource();
411 + final String response = rs.path("regions/" +
412 + region1.id().toString() + "/devices").get(String.class);
413 + final JsonObject result = Json.parse(response).asObject();
414 + assertThat(result, notNullValue());
415 +
416 + assertThat(result.names(), hasSize(1));
417 + assertThat(result.names().get(0), is("deviceIds"));
418 + final JsonArray jsonDeviceIds = result.get("deviceIds").asArray();
419 + assertThat(jsonDeviceIds.size(), is(3));
420 + assertThat(jsonDeviceIds.get(0).asString(), is("1"));
421 + assertThat(jsonDeviceIds.get(1).asString(), is("2"));
422 + assertThat(jsonDeviceIds.get(2).asString(), is("3"));
423 +
424 + verify(mockRegionService);
425 + }
426 +
427 + /**
428 + * Tests adding a set of devices in region with POST.
429 + */
430 + @Test
431 + public void testAddDevicesPost() {
432 + mockRegionAdminService.addDevices(anyObject(), anyObject());
433 + expectLastCall();
434 + replay(mockRegionAdminService);
435 +
436 + WebResource rs = resource();
437 + InputStream jsonStream = MetersResourceTest.class
438 + .getResourceAsStream("region-deviceIds.json");
439 +
440 + ClientResponse response = rs.path("regions/" +
441 + region1.id().toString() + "/devices")
442 + .type(MediaType.APPLICATION_JSON_TYPE)
443 + .post(ClientResponse.class, jsonStream);
444 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
445 +
446 + verify(mockRegionAdminService);
447 + }
448 +
449 + /**
450 + * Tests deleting a set of devices contained in the given region with DELETE.
451 + */
452 + @Test
453 + public void testRemoveDevicesDelete() {
454 + mockRegionAdminService.removeDevices(anyObject(), anyObject());
455 + expectLastCall();
456 + replay(mockRegionAdminService);
457 +
458 + WebResource rs = resource();
459 + InputStream jsonStream = MetersResourceTest.class
460 + .getResourceAsStream("region-deviceIds.json");
461 +
462 + ClientResponse response = rs.path("regions/" +
463 + region1.id().toString() + "/devices")
464 + .type(MediaType.APPLICATION_JSON_TYPE)
465 + .delete(ClientResponse.class, jsonStream);
466 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK));
467 +
468 + verify(mockRegionAdminService);
469 + }
470 +}
1 +{
2 + "id": 1,
3 + "type": "ROOM",
4 + "name": "foo",
5 + "masters": [
6 + [
7 + "1"
8 + ],
9 + [
10 + "1", "2"
11 + ]
12 + ]
13 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "deviceIds": [
3 + "of:0000000000000001",
4 + "of:0000000000000002",
5 + "of:0000000000000003"
6 + ]
7 +}
...\ No newline at end of file ...\ No newline at end of file