Ray Milkey

ONOS-790 - REST APIs for paths

Change-Id: I5e08778e75c98c1156df7972af9897b52677d371
......@@ -33,6 +33,7 @@ import org.onosproject.net.Device;
import org.onosproject.net.Host;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Link;
import org.onosproject.net.Path;
import org.onosproject.net.Port;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.TrafficSelector;
......@@ -86,6 +87,7 @@ public class CodecManager implements CodecService {
registerCodec(Constraint.class, new ConstraintCodec());
registerCodec(Topology.class, new TopologyCodec());
registerCodec(TopologyCluster.class, new TopologyClusterCodec());
registerCodec(Path.class, new PathCodec());
log.info("Started");
}
......
......@@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -30,9 +32,16 @@ public class ConnectPointCodec extends JsonCodec<ConnectPoint> {
@Override
public ObjectNode encode(ConnectPoint point, CodecContext context) {
checkNotNull(point, "Connect point cannot be null");
return context.mapper().createObjectNode()
.put("device", point.deviceId().toString())
ObjectNode root = context.mapper().createObjectNode()
.put("port", point.port().toString());
if (point.elementId() instanceof DeviceId) {
root.put("device", point.deviceId().toString());
} else if (point.elementId() instanceof HostId) {
root.put("host", point.hostId().toString());
}
return root;
}
}
......
/*
* Copyright 2015 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.Link;
import org.onosproject.net.Path;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Path JSON codec.
*/
public class PathCodec extends AnnotatedCodec<Path> {
@Override
public ObjectNode encode(Path path, CodecContext context) {
checkNotNull(path, "Path cannot be null");
JsonCodec<Link> codec = context.codec(Link.class);
ObjectNode result = context.mapper()
.createObjectNode()
.put("cost", path.cost());
ArrayNode jsonLinks = result.putArray("links");
for (Link link : path.links()) {
jsonLinks.add(codec.encode(link, context));
}
return annotate(result, path, context);
}
}
/*
* Copyright 2015 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.Set;
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.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.HostId;
import org.onosproject.net.topology.PathService;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* REST resource for interacting with path calculations.
*/
@Path("paths")
public class PathsWebResource extends AbstractWebResource {
// Host id format is 00:00:00:00:00:01/-1
private static final int VLAN_SEPARATOR_OFFSET = 17;
private static final int FIRST_MAC_ADDRESS_SEPARATOR_OFFSET = 2;
private static final int SECOND_MAC_ADDRESS_SEPARATOR_OFFSET = 5;
private static final int THIRD_MAC_ADDRESS_SEPARATOR_OFFSET = 8;
/**
* Determines if the id appears to be the id of a host.
*
* @param id id string
* @return HostId if the id is valid, null otherwise
*/
private HostId isHostId(String id) {
if (id.length() < VLAN_SEPARATOR_OFFSET + 1 ||
id.charAt(FIRST_MAC_ADDRESS_SEPARATOR_OFFSET) != ':' ||
id.charAt(SECOND_MAC_ADDRESS_SEPARATOR_OFFSET) != ':' ||
id.charAt(THIRD_MAC_ADDRESS_SEPARATOR_OFFSET) != ':' ||
id.charAt(VLAN_SEPARATOR_OFFSET) != '/') {
return null;
}
return HostId.hostId(id);
}
/**
* Gets the paths between two elements.
*
* @param src source
* @param dst destination
* @return path data
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{src}/{dst}")
public Response getPath(@PathParam("src") String src,
@PathParam("dst") String dst) {
PathService pathService = get(PathService.class);
ElementId srcElement = isHostId(src);
ElementId dstElement = isHostId(dst);
if (srcElement == null) {
// Doesn't look like a host, assume it is a device
srcElement = DeviceId.deviceId(src);
}
if (dstElement == null) {
// Doesn't look like a host, assume it is a device
dstElement = DeviceId.deviceId(dst);
}
Set<org.onosproject.net.Path> paths =
pathService.getPaths(srcElement, dstElement);
ObjectNode root =
encodeArray(org.onosproject.net.Path.class, "paths", paths);
return ok(root).build();
}
}
......@@ -43,7 +43,8 @@
org.onosproject.rest.IntentsWebResource,
org.onosproject.rest.FlowsWebResource,
org.onosproject.rest.TopologyWebResource,
org.onosproject.rest.ConfigResource
org.onosproject.rest.ConfigResource,
org.onosproject.rest.PathsWebResource
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
......
/*
* Copyright 2015 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.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeDiagnosingMatcher;
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.net.ElementId;
import org.onosproject.net.Link;
import org.onosproject.net.Path;
import org.onosproject.net.topology.PathService;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.ImmutableSet;
import com.sun.jersey.api.client.WebResource;
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.onosproject.net.NetTestTools.createPath;
import static org.onosproject.net.NetTestTools.did;
import static org.onosproject.net.NetTestTools.hid;
/**
* Unit tests for paths REST APIs.
*/
public class PathsResourceTest extends ResourceTest {
Path path1 = createPath("dev1", "dev2");
Path path2 = createPath("dev2", "dev3");
Set<Path> paths = ImmutableSet.of(path1, path2);
final PathService mockPathService = createMock(PathService.class);
/**
* Hamcrest matcher for a path and its JSON representation.
*/
private final class PathJsonMatcher extends TypeSafeDiagnosingMatcher<JsonObject> {
private final Path path;
/**
* Creates the matcher.
*
* @param pathValue the path object to match
*/
private PathJsonMatcher(Path pathValue) {
path = pathValue;
}
@Override
public boolean matchesSafely(JsonObject pathJson, Description description) {
double jsonCost = pathJson.get("cost").asDouble();
if (jsonCost != path.cost()) {
description.appendText("src device was " + jsonCost);
return false;
}
JsonArray jsonLinks = pathJson.get("links").asArray();
assertThat(jsonLinks.size(), is(path.links().size()));
for (int linkIndex = 0; linkIndex < jsonLinks.size(); linkIndex++) {
Link link = path.links().get(linkIndex);
JsonObject jsonLink = jsonLinks.get(0).asObject();
JsonObject jsonLinkSrc = jsonLink.get("src").asObject();
String srcDevice = jsonLinkSrc.get("device").asString();
if (!srcDevice.equals(link.src().deviceId().toString())) {
description.appendText("src device was " + jsonLinkSrc);
return false;
}
JsonObject jsonLinkDst = jsonLink.get("dst").asObject();
String dstDevice = jsonLinkDst.get("device").asString();
if (!dstDevice.equals(link.dst().deviceId().toString())) {
description.appendText("dst device was " + jsonLinkDst);
return false;
}
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText(path.toString());
}
}
/**
* Factory to allocate an connect point matcher.
*
* @param path path object we are looking for
* @return matcher
*/
private PathJsonMatcher matchesPath(Path path) {
return new PathJsonMatcher(path);
}
@Before
public void setUp() {
// Register the services needed for the test
CodecManager codecService = new CodecManager();
codecService.activate();
ServiceDirectory testDirectory =
new TestServiceDirectory()
.add(PathService.class, mockPathService)
.add(CodecService.class, codecService);
BaseResource.setServiceDirectory(testDirectory);
}
@After
public void tearDown() throws Exception {
super.tearDown();
verify(mockPathService);
}
/**
* Tests a REST path GET for the given endpoints.
*
* @param srcElement source element of the path
* @param dstElement destination element of the path
*
* @throws UnsupportedEncodingException
*/
private void runTest(ElementId srcElement, ElementId dstElement)
throws UnsupportedEncodingException {
expect(mockPathService.getPaths(srcElement, dstElement))
.andReturn(paths)
.once();
replay(mockPathService);
String srcId = URLEncoder.encode(srcElement.toString(),
StandardCharsets.UTF_8.name());
String dstId = URLEncoder.encode(dstElement.toString(),
StandardCharsets.UTF_8.name());
String url = "paths/" + srcId + "/" + dstId;
WebResource rs = resource();
String response = rs.path(url).get(String.class);
assertThat(response, containsString("{\"paths\":["));
JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("paths"));
JsonArray jsonPaths = result.get("paths").asArray();
assertThat(jsonPaths, notNullValue());
assertThat(jsonPaths.size(), is(2));
JsonObject path1Json = jsonPaths.get(0).asObject();
assertThat(path1Json, matchesPath(path1));
JsonObject path2Json = jsonPaths.get(1).asObject();
assertThat(path2Json, matchesPath(path2));
}
/**
* Tests a path between two hosts.
*
* @throws UnsupportedEncodingException if UTF-8 not found
*/
@Test
public void hostToHost() throws UnsupportedEncodingException {
runTest(hid("01:23:45:67:89:AB/2"), hid("AB:89:67:45:23:01/4"));
}
/**
* Tests a path with a host as the source and a switch as the destination.
*
* @throws UnsupportedEncodingException if UTF-8 not found
*/
@Test
public void hostToDevice() throws UnsupportedEncodingException {
runTest(hid("01:23:45:67:89:AB/2"), did("switch1"));
}
/**
* Tests a path with a switch as the source and a host as the destination.
*
* @throws UnsupportedEncodingException if UTF-8 not found
*/
@Test
public void deviceToHost() throws UnsupportedEncodingException {
runTest(did("switch1"), hid("01:23:45:67:89:AB/2"));
}
/**
* Tests a path between two switches.
*
* @throws UnsupportedEncodingException if UTF-8 not found
*/
@Test
public void deviceToDevice() throws UnsupportedEncodingException {
runTest(did("switch1"), did("switch2"));
}
}