Ray Milkey

Implement GET REST API for intents

- GET /intent and GET /intent/id implemented
- Basic codec for Intents
- Unit tests

Change-Id: Idbe59d4f7a792c88c77d6f3498d01d4cb7c4467d
...@@ -29,6 +29,7 @@ import org.onosproject.net.Host; ...@@ -29,6 +29,7 @@ import org.onosproject.net.Host;
29 import org.onosproject.net.HostLocation; 29 import org.onosproject.net.HostLocation;
30 import org.onosproject.net.Link; 30 import org.onosproject.net.Link;
31 import org.onosproject.net.Port; 31 import org.onosproject.net.Port;
32 +import org.onosproject.net.intent.Intent;
32 import org.slf4j.Logger; 33 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory; 34 import org.slf4j.LoggerFactory;
34 35
...@@ -57,6 +58,7 @@ public class CodecManager implements CodecService { ...@@ -57,6 +58,7 @@ public class CodecManager implements CodecService {
57 registerCodec(Link.class, new LinkCodec()); 58 registerCodec(Link.class, new LinkCodec());
58 registerCodec(Host.class, new HostCodec()); 59 registerCodec(Host.class, new HostCodec());
59 registerCodec(HostLocation.class, new HostLocationCodec()); 60 registerCodec(HostLocation.class, new HostLocationCodec());
61 + registerCodec(Intent.class, new IntentCodec());
60 log.info("Started"); 62 log.info("Started");
61 } 63 }
62 64
......
1 +/*
2 + * Copyright 2014 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.codec.impl;
17 +
18 +import org.onosproject.codec.CodecContext;
19 +import org.onosproject.codec.JsonCodec;
20 +import org.onosproject.net.NetworkResource;
21 +import org.onosproject.net.intent.Intent;
22 +
23 +import com.fasterxml.jackson.databind.node.ArrayNode;
24 +import com.fasterxml.jackson.databind.node.ObjectNode;
25 +
26 +import static com.google.common.base.Preconditions.checkNotNull;
27 +
28 +/**
29 + * Intent JSON codec.
30 + */
31 +public class IntentCodec extends JsonCodec<Intent> {
32 +
33 + @Override
34 + public ObjectNode encode(Intent intent, CodecContext context) {
35 + checkNotNull(intent, "Intent cannot be null");
36 + final ObjectNode result = context.mapper().createObjectNode()
37 + .put("type", intent.getClass().getSimpleName())
38 + .put("id", intent.id().toString())
39 + .put("appId", intent.appId().toString())
40 + .put("details", intent.toString());
41 +
42 + final ArrayNode jsonResources = result.putArray("resources");
43 +
44 + if (intent.resources() != null) {
45 + for (final NetworkResource resource : intent.resources()) {
46 + jsonResources.add(resource.toString());
47 + }
48 + }
49 + return result;
50 + }
51 +}
1 +/*
2 + * Copyright 2014 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 javax.ws.rs.GET;
19 +import javax.ws.rs.Path;
20 +import javax.ws.rs.PathParam;
21 +import javax.ws.rs.Produces;
22 +import javax.ws.rs.core.MediaType;
23 +import javax.ws.rs.core.Response;
24 +
25 +import org.onosproject.net.intent.Intent;
26 +import org.onosproject.net.intent.IntentId;
27 +import org.onosproject.net.intent.IntentService;
28 +
29 +import com.fasterxml.jackson.databind.node.ObjectNode;
30 +
31 +/**
32 + * REST resource for interacting with the inventory of intents.
33 + */
34 +
35 +@Path("intents")
36 +public class IntentsWebResource extends AbstractWebResource {
37 + public static final String INTENT_NOT_FOUND = "Intent is not found";
38 +
39 + /**
40 + * Gets an array containing all the intents in the system.
41 + *
42 + * @return array of all the intents in the system
43 + */
44 + @GET
45 + @Produces(MediaType.APPLICATION_JSON)
46 + public Response getIntents() {
47 + final Iterable<Intent> intents = get(IntentService.class).getIntents();
48 + final ObjectNode root = encodeArray(Intent.class, "intents", intents);
49 + return ok(root.toString()).build();
50 + }
51 +
52 + /**
53 + * Gets a single intent by Id.
54 + *
55 + * @param id Id to look up
56 + * @return intent data
57 + */
58 + @GET
59 + @Produces(MediaType.APPLICATION_JSON)
60 + @Path("{id}")
61 + public Response getHostById(@PathParam("id") long id) {
62 + final Intent intent = nullIsNotFound(get(IntentService.class)
63 + .getIntent(IntentId.valueOf(id)),
64 + INTENT_NOT_FOUND);
65 + final ObjectNode root = codec(Intent.class).encode(intent, this);
66 + return ok(root.toString()).build();
67 + }
68 +}
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
38 org.onosproject.rest.DevicesWebResource, 38 org.onosproject.rest.DevicesWebResource,
39 org.onosproject.rest.LinksWebResource, 39 org.onosproject.rest.LinksWebResource,
40 org.onosproject.rest.HostsWebResource, 40 org.onosproject.rest.HostsWebResource,
41 + org.onosproject.rest.IntentsWebResource,
41 org.onosproject.rest.ConfigResource 42 org.onosproject.rest.ConfigResource
42 </param-value> 43 </param-value>
43 </init-param> 44 </init-param>
......
1 +/*
2 + * Copyright 2014 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 java.util.Collection;
19 +import java.util.HashSet;
20 +import java.util.concurrent.atomic.AtomicLong;
21 +
22 +import org.hamcrest.Description;
23 +import org.hamcrest.TypeSafeMatcher;
24 +import org.junit.After;
25 +import org.junit.Before;
26 +import org.junit.Test;
27 +import org.onlab.osgi.ServiceDirectory;
28 +import org.onlab.osgi.TestServiceDirectory;
29 +import org.onlab.rest.BaseResource;
30 +import org.onosproject.codec.CodecService;
31 +import org.onosproject.codec.impl.CodecManager;
32 +import org.onosproject.core.ApplicationId;
33 +import org.onosproject.core.DefaultApplicationId;
34 +import org.onosproject.core.IdGenerator;
35 +import org.onosproject.net.NetworkResource;
36 +import org.onosproject.net.intent.Intent;
37 +import org.onosproject.net.intent.IntentId;
38 +import org.onosproject.net.intent.IntentService;
39 +
40 +import com.eclipsesource.json.JsonArray;
41 +import com.eclipsesource.json.JsonObject;
42 +import com.eclipsesource.json.JsonValue;
43 +import com.google.common.base.MoreObjects;
44 +import com.sun.jersey.api.client.UniformInterfaceException;
45 +import com.sun.jersey.api.client.WebResource;
46 +import com.sun.jersey.test.framework.JerseyTest;
47 +
48 +import static org.easymock.EasyMock.createMock;
49 +import static org.easymock.EasyMock.expect;
50 +import static org.easymock.EasyMock.replay;
51 +import static org.easymock.EasyMock.verify;
52 +import static org.hamcrest.Matchers.containsString;
53 +import static org.hamcrest.Matchers.hasSize;
54 +import static org.hamcrest.Matchers.is;
55 +import static org.hamcrest.Matchers.notNullValue;
56 +import static org.junit.Assert.assertThat;
57 +import static org.junit.Assert.fail;
58 +
59 +/**
60 + * Unit tests for Intents REST APIs.
61 + */
62 +public class IntentsResourceTest extends JerseyTest {
63 + final IntentService mockIntentService = createMock(IntentService.class);
64 + final HashSet<Intent> intents = new HashSet<>();
65 + private static final ApplicationId APP_ID =
66 + new DefaultApplicationId((short) 1, "test");
67 + private IdGenerator mockGenerator;
68 +
69 + /**
70 + * Mock ID generator. This should be refactored to share the one in
71 + * the core/api tests.
72 + */
73 + public class MockIdGenerator implements IdGenerator {
74 + private AtomicLong nextId = new AtomicLong(0);
75 +
76 + @Override
77 + public long getNewId() {
78 + return nextId.getAndIncrement();
79 + }
80 + }
81 +
82 + /**
83 + * Mock compilable intent class.
84 + */
85 + private static class MockIntent extends Intent {
86 +
87 + public MockIntent(Collection<NetworkResource> resources) {
88 + super(APP_ID, resources);
89 + }
90 +
91 + @Override
92 + public String toString() {
93 + return MoreObjects.toStringHelper(getClass())
94 + .add("id", id())
95 + .add("appId", appId())
96 + .toString();
97 + }
98 + }
99 +
100 + private class MockResource implements NetworkResource {
101 + int id;
102 +
103 + MockResource(int id) {
104 + this.id = id;
105 + }
106 +
107 + public String toString() {
108 + return "Resource " + Integer.toString(id);
109 + }
110 + }
111 +
112 + public IntentsResourceTest() {
113 + super("org.onosproject.rest");
114 + }
115 +
116 + /**
117 + * Hamcrest matcher to check that an intent representation in JSON matches
118 + * the actual intent.
119 + */
120 + public static class IntentJsonMatcher extends TypeSafeMatcher<JsonObject> {
121 + private final Intent intent;
122 + private String reason = "";
123 +
124 + public IntentJsonMatcher(Intent intentValue) {
125 + intent = intentValue;
126 + }
127 +
128 + @Override
129 + public boolean matchesSafely(JsonObject jsonIntent) {
130 + // check id
131 + final String jsonId = jsonIntent.get("id").asString();
132 + if (!jsonId.equals(intent.id().toString())) {
133 + reason = "id " + intent.id().toString();
134 + return false;
135 + }
136 +
137 + // check application id
138 + final String jsonAppId = jsonIntent.get("appId").asString();
139 + if (!jsonAppId.equals(intent.appId().toString())) {
140 + reason = "appId " + intent.appId().toString();
141 + return false;
142 + }
143 +
144 + // check intent type
145 + final String jsonType = jsonIntent.get("type").asString();
146 + if (!jsonType.equals("MockIntent")) {
147 + reason = "type MockIntent";
148 + return false;
149 + }
150 +
151 + // check details field
152 + final String jsonDetails = jsonIntent.get("details").asString();
153 + if (!jsonDetails.equals(intent.toString())) {
154 + reason = "details " + intent.toString();
155 + return false;
156 + }
157 +
158 + // check resources array
159 + final JsonArray jsonResources = jsonIntent.get("resources").asArray();
160 + if (intent.resources() != null) {
161 + if (intent.resources().size() != jsonResources.size()) {
162 + reason = "resources array size of " + Integer.toString(intent.resources().size());
163 + return false;
164 + }
165 + for (final NetworkResource resource : intent.resources()) {
166 + boolean resourceFound = false;
167 + final String resourceString = resource.toString();
168 + for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) {
169 + final JsonValue value = jsonResources.get(resourceIndex);
170 + if (value.asString().equals(resourceString)) {
171 + resourceFound = true;
172 + }
173 + }
174 + if (!resourceFound) {
175 + reason = "resource " + resourceString;
176 + return false;
177 + }
178 + }
179 + } else if (jsonResources.size() != 0) {
180 + reason = "resources array empty";
181 + return false;
182 + }
183 + return true;
184 + }
185 +
186 + @Override
187 + public void describeTo(Description description) {
188 + description.appendText(reason);
189 + }
190 + }
191 +
192 + /**
193 + * Factory to allocate an intent matcher.
194 + *
195 + * @param intent intent object we are looking for
196 + * @return matcher
197 + */
198 + private static IntentJsonMatcher matchesIntent(Intent intent) {
199 + return new IntentJsonMatcher(intent);
200 + }
201 +
202 + /**
203 + * Hamcrest matcher to check that an intent is represented properly in a JSON
204 + * array of intents.
205 + */
206 + public static class IntentJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
207 + private final Intent intent;
208 + private String reason = "";
209 +
210 + public IntentJsonArrayMatcher(Intent intentValue) {
211 + intent = intentValue;
212 + }
213 +
214 + @Override
215 + public boolean matchesSafely(JsonArray json) {
216 + boolean intentFound = false;
217 + final int expectedAttributes = 5;
218 + for (int jsonIntentIndex = 0; jsonIntentIndex < json.size();
219 + jsonIntentIndex++) {
220 +
221 + final JsonObject jsonIntent = json.get(jsonIntentIndex).asObject();
222 +
223 + if (jsonIntent.names().size() != expectedAttributes) {
224 + reason = "Found an intent with the wrong number of attributes";
225 + return false;
226 + }
227 +
228 + final String jsonIntentId = jsonIntent.get("id").asString();
229 + if (jsonIntentId.equals(intent.id().toString())) {
230 + intentFound = true;
231 +
232 + // We found the correct intent, check attribute values
233 + assertThat(jsonIntent, matchesIntent(intent));
234 + }
235 + }
236 + if (!intentFound) {
237 + reason = "Intent with id " + intent.id().toString() + " not found";
238 + return false;
239 + } else {
240 + return true;
241 + }
242 + }
243 +
244 + @Override
245 + public void describeTo(Description description) {
246 + description.appendText(reason);
247 + }
248 + }
249 +
250 + /**
251 + * Factory to allocate an intent array matcher.
252 + *
253 + * @param intent intent object we are looking for
254 + * @return matcher
255 + */
256 + private static IntentJsonArrayMatcher hasIntent(Intent intent) {
257 + return new IntentJsonArrayMatcher(intent);
258 + }
259 + @Before
260 + public void setUp() {
261 + expect(mockIntentService.getIntents()).andReturn(intents).anyTimes();
262 +
263 + // Register the services needed for the test
264 + final CodecManager codecService = new CodecManager();
265 + codecService.activate();
266 + ServiceDirectory testDirectory =
267 + new TestServiceDirectory()
268 + .add(IntentService.class, mockIntentService)
269 + .add(CodecService.class, codecService);
270 +
271 + BaseResource.setServiceDirectory(testDirectory);
272 +
273 + mockGenerator = new MockIdGenerator();
274 + Intent.bindIdGenerator(mockGenerator);
275 + }
276 +
277 + @After
278 + public void tearDown() throws Exception {
279 + super.tearDown();
280 + verify(mockIntentService);
281 + Intent.unbindIdGenerator(mockGenerator);
282 + }
283 +
284 + /**
285 + * Tests the result of the rest api GET when there are no intents.
286 + */
287 + @Test
288 + public void testIntentsEmptyArray() {
289 + replay(mockIntentService);
290 + final WebResource rs = resource();
291 + final String response = rs.path("intents").get(String.class);
292 + assertThat(response, is("{\"intents\":[]}"));
293 + }
294 +
295 + /**
296 + * Tests the result of the rest api GET when intents are defined.
297 + */
298 + @Test
299 + public void testIntentsArray() {
300 + replay(mockIntentService);
301 +
302 + final Intent intent1 = new MockIntent(null);
303 + final HashSet<NetworkResource> resources = new HashSet<>();
304 + resources.add(new MockResource(1));
305 + resources.add(new MockResource(2));
306 + resources.add(new MockResource(3));
307 + final Intent intent2 = new MockIntent(resources);
308 +
309 + intents.add(intent1);
310 + intents.add(intent2);
311 + final WebResource rs = resource();
312 + final String response = rs.path("intents").get(String.class);
313 + assertThat(response, containsString("{\"intents\":["));
314 +
315 + final JsonObject result = JsonObject.readFrom(response);
316 + assertThat(result, notNullValue());
317 +
318 + assertThat(result.names(), hasSize(1));
319 + assertThat(result.names().get(0), is("intents"));
320 +
321 + final JsonArray jsonIntents = result.get("intents").asArray();
322 + assertThat(jsonIntents, notNullValue());
323 +
324 + assertThat(jsonIntents, hasIntent(intent1));
325 + assertThat(jsonIntents, hasIntent(intent2));
326 + }
327 +
328 + /**
329 + * Tests the result of a rest api GET for a single intent.
330 + */
331 + @Test
332 + public void testIntentsSingle() {
333 + final HashSet<NetworkResource> resources = new HashSet<>();
334 + resources.add(new MockResource(1));
335 + resources.add(new MockResource(2));
336 + resources.add(new MockResource(3));
337 + final Intent intent = new MockIntent(resources);
338 +
339 + intents.add(intent);
340 +
341 + expect(mockIntentService.getIntent(IntentId.valueOf(0)))
342 + .andReturn(intent)
343 + .anyTimes();
344 + replay(mockIntentService);
345 +
346 + final WebResource rs = resource();
347 + final String response = rs.path("intents/0").get(String.class);
348 + final JsonObject result = JsonObject.readFrom(response);
349 + assertThat(result, matchesIntent(intent));
350 + }
351 +
352 + /**
353 + * Tests that a fetch of a non-existent intent object throws an exception.
354 + */
355 + @Test
356 + public void testBadGet() {
357 +
358 + expect(mockIntentService.getIntent(IntentId.valueOf(0)))
359 + .andReturn(null)
360 + .anyTimes();
361 + replay(mockIntentService);
362 +
363 + WebResource rs = resource();
364 + try {
365 + rs.path("intents/0").get(String.class);
366 + fail("Fetch of non-existent intent did not throw an exception");
367 + } catch (UniformInterfaceException ex) {
368 + assertThat(ex.getMessage(),
369 + containsString("returned a response status of"));
370 + }
371 + }
372 +}