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;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Link;
import org.onosproject.net.Port;
import org.onosproject.net.intent.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -57,6 +58,7 @@ public class CodecManager implements CodecService {
registerCodec(Link.class, new LinkCodec());
registerCodec(Host.class, new HostCodec());
registerCodec(HostLocation.class, new HostLocationCodec());
registerCodec(Intent.class, new IntentCodec());
log.info("Started");
}
......
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.codec.impl;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.intent.Intent;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Intent JSON codec.
*/
public class IntentCodec extends JsonCodec<Intent> {
@Override
public ObjectNode encode(Intent intent, CodecContext context) {
checkNotNull(intent, "Intent cannot be null");
final ObjectNode result = context.mapper().createObjectNode()
.put("type", intent.getClass().getSimpleName())
.put("id", intent.id().toString())
.put("appId", intent.appId().toString())
.put("details", intent.toString());
final ArrayNode jsonResources = result.putArray("resources");
if (intent.resources() != null) {
for (final NetworkResource resource : intent.resources()) {
jsonResources.add(resource.toString());
}
}
return result;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentId;
import org.onosproject.net.intent.IntentService;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* REST resource for interacting with the inventory of intents.
*/
@Path("intents")
public class IntentsWebResource extends AbstractWebResource {
public static final String INTENT_NOT_FOUND = "Intent is not found";
/**
* Gets an array containing all the intents in the system.
*
* @return array of all the intents in the system
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getIntents() {
final Iterable<Intent> intents = get(IntentService.class).getIntents();
final ObjectNode root = encodeArray(Intent.class, "intents", intents);
return ok(root.toString()).build();
}
/**
* Gets a single intent by Id.
*
* @param id Id to look up
* @return intent data
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{id}")
public Response getHostById(@PathParam("id") long id) {
final Intent intent = nullIsNotFound(get(IntentService.class)
.getIntent(IntentId.valueOf(id)),
INTENT_NOT_FOUND);
final ObjectNode root = codec(Intent.class).encode(intent, this);
return ok(root.toString()).build();
}
}
......@@ -38,6 +38,7 @@
org.onosproject.rest.DevicesWebResource,
org.onosproject.rest.LinksWebResource,
org.onosproject.rest.HostsWebResource,
org.onosproject.rest.IntentsWebResource,
org.onosproject.rest.ConfigResource
</param-value>
</init-param>
......
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.rest;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicLong;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.osgi.TestServiceDirectory;
import org.onlab.rest.BaseResource;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.core.IdGenerator;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentId;
import org.onosproject.net.intent.IntentService;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import com.google.common.base.MoreObjects;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.test.framework.JerseyTest;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Unit tests for Intents REST APIs.
*/
public class IntentsResourceTest extends JerseyTest {
final IntentService mockIntentService = createMock(IntentService.class);
final HashSet<Intent> intents = new HashSet<>();
private static final ApplicationId APP_ID =
new DefaultApplicationId((short) 1, "test");
private IdGenerator mockGenerator;
/**
* Mock ID generator. This should be refactored to share the one in
* the core/api tests.
*/
public class MockIdGenerator implements IdGenerator {
private AtomicLong nextId = new AtomicLong(0);
@Override
public long getNewId() {
return nextId.getAndIncrement();
}
}
/**
* Mock compilable intent class.
*/
private static class MockIntent extends Intent {
public MockIntent(Collection<NetworkResource> resources) {
super(APP_ID, resources);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("id", id())
.add("appId", appId())
.toString();
}
}
private class MockResource implements NetworkResource {
int id;
MockResource(int id) {
this.id = id;
}
public String toString() {
return "Resource " + Integer.toString(id);
}
}
public IntentsResourceTest() {
super("org.onosproject.rest");
}
/**
* Hamcrest matcher to check that an intent representation in JSON matches
* the actual intent.
*/
public static class IntentJsonMatcher extends TypeSafeMatcher<JsonObject> {
private final Intent intent;
private String reason = "";
public IntentJsonMatcher(Intent intentValue) {
intent = intentValue;
}
@Override
public boolean matchesSafely(JsonObject jsonIntent) {
// check id
final String jsonId = jsonIntent.get("id").asString();
if (!jsonId.equals(intent.id().toString())) {
reason = "id " + intent.id().toString();
return false;
}
// check application id
final String jsonAppId = jsonIntent.get("appId").asString();
if (!jsonAppId.equals(intent.appId().toString())) {
reason = "appId " + intent.appId().toString();
return false;
}
// check intent type
final String jsonType = jsonIntent.get("type").asString();
if (!jsonType.equals("MockIntent")) {
reason = "type MockIntent";
return false;
}
// check details field
final String jsonDetails = jsonIntent.get("details").asString();
if (!jsonDetails.equals(intent.toString())) {
reason = "details " + intent.toString();
return false;
}
// check resources array
final JsonArray jsonResources = jsonIntent.get("resources").asArray();
if (intent.resources() != null) {
if (intent.resources().size() != jsonResources.size()) {
reason = "resources array size of " + Integer.toString(intent.resources().size());
return false;
}
for (final NetworkResource resource : intent.resources()) {
boolean resourceFound = false;
final String resourceString = resource.toString();
for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) {
final JsonValue value = jsonResources.get(resourceIndex);
if (value.asString().equals(resourceString)) {
resourceFound = true;
}
}
if (!resourceFound) {
reason = "resource " + resourceString;
return false;
}
}
} else if (jsonResources.size() != 0) {
reason = "resources array empty";
return false;
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText(reason);
}
}
/**
* Factory to allocate an intent matcher.
*
* @param intent intent object we are looking for
* @return matcher
*/
private static IntentJsonMatcher matchesIntent(Intent intent) {
return new IntentJsonMatcher(intent);
}
/**
* Hamcrest matcher to check that an intent is represented properly in a JSON
* array of intents.
*/
public static class IntentJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
private final Intent intent;
private String reason = "";
public IntentJsonArrayMatcher(Intent intentValue) {
intent = intentValue;
}
@Override
public boolean matchesSafely(JsonArray json) {
boolean intentFound = false;
final int expectedAttributes = 5;
for (int jsonIntentIndex = 0; jsonIntentIndex < json.size();
jsonIntentIndex++) {
final JsonObject jsonIntent = json.get(jsonIntentIndex).asObject();
if (jsonIntent.names().size() != expectedAttributes) {
reason = "Found an intent with the wrong number of attributes";
return false;
}
final String jsonIntentId = jsonIntent.get("id").asString();
if (jsonIntentId.equals(intent.id().toString())) {
intentFound = true;
// We found the correct intent, check attribute values
assertThat(jsonIntent, matchesIntent(intent));
}
}
if (!intentFound) {
reason = "Intent with id " + intent.id().toString() + " not found";
return false;
} else {
return true;
}
}
@Override
public void describeTo(Description description) {
description.appendText(reason);
}
}
/**
* Factory to allocate an intent array matcher.
*
* @param intent intent object we are looking for
* @return matcher
*/
private static IntentJsonArrayMatcher hasIntent(Intent intent) {
return new IntentJsonArrayMatcher(intent);
}
@Before
public void setUp() {
expect(mockIntentService.getIntents()).andReturn(intents).anyTimes();
// Register the services needed for the test
final CodecManager codecService = new CodecManager();
codecService.activate();
ServiceDirectory testDirectory =
new TestServiceDirectory()
.add(IntentService.class, mockIntentService)
.add(CodecService.class, codecService);
BaseResource.setServiceDirectory(testDirectory);
mockGenerator = new MockIdGenerator();
Intent.bindIdGenerator(mockGenerator);
}
@After
public void tearDown() throws Exception {
super.tearDown();
verify(mockIntentService);
Intent.unbindIdGenerator(mockGenerator);
}
/**
* Tests the result of the rest api GET when there are no intents.
*/
@Test
public void testIntentsEmptyArray() {
replay(mockIntentService);
final WebResource rs = resource();
final String response = rs.path("intents").get(String.class);
assertThat(response, is("{\"intents\":[]}"));
}
/**
* Tests the result of the rest api GET when intents are defined.
*/
@Test
public void testIntentsArray() {
replay(mockIntentService);
final Intent intent1 = new MockIntent(null);
final HashSet<NetworkResource> resources = new HashSet<>();
resources.add(new MockResource(1));
resources.add(new MockResource(2));
resources.add(new MockResource(3));
final Intent intent2 = new MockIntent(resources);
intents.add(intent1);
intents.add(intent2);
final WebResource rs = resource();
final String response = rs.path("intents").get(String.class);
assertThat(response, containsString("{\"intents\":["));
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("intents"));
final JsonArray jsonIntents = result.get("intents").asArray();
assertThat(jsonIntents, notNullValue());
assertThat(jsonIntents, hasIntent(intent1));
assertThat(jsonIntents, hasIntent(intent2));
}
/**
* Tests the result of a rest api GET for a single intent.
*/
@Test
public void testIntentsSingle() {
final HashSet<NetworkResource> resources = new HashSet<>();
resources.add(new MockResource(1));
resources.add(new MockResource(2));
resources.add(new MockResource(3));
final Intent intent = new MockIntent(resources);
intents.add(intent);
expect(mockIntentService.getIntent(IntentId.valueOf(0)))
.andReturn(intent)
.anyTimes();
replay(mockIntentService);
final WebResource rs = resource();
final String response = rs.path("intents/0").get(String.class);
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, matchesIntent(intent));
}
/**
* Tests that a fetch of a non-existent intent object throws an exception.
*/
@Test
public void testBadGet() {
expect(mockIntentService.getIntent(IntentId.valueOf(0)))
.andReturn(null)
.anyTimes();
replay(mockIntentService);
WebResource rs = resource();
try {
rs.path("intents/0").get(String.class);
fail("Fetch of non-existent intent did not throw an exception");
} catch (UniformInterfaceException ex) {
assertThat(ex.getMessage(),
containsString("returned a response status of"));
}
}
}