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 + "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