Ray Milkey

ONOS-790 - REST APIs for paths

Change-Id: I5e08778e75c98c1156df7972af9897b52677d371
...@@ -33,6 +33,7 @@ import org.onosproject.net.Device; ...@@ -33,6 +33,7 @@ import org.onosproject.net.Device;
33 import org.onosproject.net.Host; 33 import org.onosproject.net.Host;
34 import org.onosproject.net.HostLocation; 34 import org.onosproject.net.HostLocation;
35 import org.onosproject.net.Link; 35 import org.onosproject.net.Link;
36 +import org.onosproject.net.Path;
36 import org.onosproject.net.Port; 37 import org.onosproject.net.Port;
37 import org.onosproject.net.flow.FlowEntry; 38 import org.onosproject.net.flow.FlowEntry;
38 import org.onosproject.net.flow.TrafficSelector; 39 import org.onosproject.net.flow.TrafficSelector;
...@@ -86,6 +87,7 @@ public class CodecManager implements CodecService { ...@@ -86,6 +87,7 @@ public class CodecManager implements CodecService {
86 registerCodec(Constraint.class, new ConstraintCodec()); 87 registerCodec(Constraint.class, new ConstraintCodec());
87 registerCodec(Topology.class, new TopologyCodec()); 88 registerCodec(Topology.class, new TopologyCodec());
88 registerCodec(TopologyCluster.class, new TopologyClusterCodec()); 89 registerCodec(TopologyCluster.class, new TopologyClusterCodec());
90 + registerCodec(Path.class, new PathCodec());
89 log.info("Started"); 91 log.info("Started");
90 } 92 }
91 93
......
...@@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; ...@@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
19 import org.onosproject.codec.CodecContext; 19 import org.onosproject.codec.CodecContext;
20 import org.onosproject.codec.JsonCodec; 20 import org.onosproject.codec.JsonCodec;
21 import org.onosproject.net.ConnectPoint; 21 import org.onosproject.net.ConnectPoint;
22 +import org.onosproject.net.DeviceId;
23 +import org.onosproject.net.HostId;
22 24
23 import static com.google.common.base.Preconditions.checkNotNull; 25 import static com.google.common.base.Preconditions.checkNotNull;
24 26
...@@ -30,9 +32,16 @@ public class ConnectPointCodec extends JsonCodec<ConnectPoint> { ...@@ -30,9 +32,16 @@ public class ConnectPointCodec extends JsonCodec<ConnectPoint> {
30 @Override 32 @Override
31 public ObjectNode encode(ConnectPoint point, CodecContext context) { 33 public ObjectNode encode(ConnectPoint point, CodecContext context) {
32 checkNotNull(point, "Connect point cannot be null"); 34 checkNotNull(point, "Connect point cannot be null");
33 - return context.mapper().createObjectNode() 35 + ObjectNode root = context.mapper().createObjectNode()
34 - .put("device", point.deviceId().toString())
35 .put("port", point.port().toString()); 36 .put("port", point.port().toString());
37 +
38 + if (point.elementId() instanceof DeviceId) {
39 + root.put("device", point.deviceId().toString());
40 + } else if (point.elementId() instanceof HostId) {
41 + root.put("host", point.hostId().toString());
42 + }
43 +
44 + return root;
36 } 45 }
37 46
38 } 47 }
......
1 +/*
2 + * Copyright 2015 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.Link;
21 +import org.onosproject.net.Path;
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 + * Path JSON codec.
30 + */
31 +public class PathCodec extends AnnotatedCodec<Path> {
32 + @Override
33 + public ObjectNode encode(Path path, CodecContext context) {
34 + checkNotNull(path, "Path cannot be null");
35 + JsonCodec<Link> codec = context.codec(Link.class);
36 + ObjectNode result = context.mapper()
37 + .createObjectNode()
38 + .put("cost", path.cost());
39 + ArrayNode jsonLinks = result.putArray("links");
40 +
41 + for (Link link : path.links()) {
42 + jsonLinks.add(codec.encode(link, context));
43 + }
44 +
45 + return annotate(result, path, context);
46 + }
47 +}
1 +/*
2 + * Copyright 2015 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.Set;
19 +
20 +import javax.ws.rs.GET;
21 +import javax.ws.rs.Path;
22 +import javax.ws.rs.PathParam;
23 +import javax.ws.rs.Produces;
24 +import javax.ws.rs.core.MediaType;
25 +import javax.ws.rs.core.Response;
26 +
27 +import org.onosproject.net.DeviceId;
28 +import org.onosproject.net.ElementId;
29 +import org.onosproject.net.HostId;
30 +import org.onosproject.net.topology.PathService;
31 +
32 +import com.fasterxml.jackson.databind.node.ObjectNode;
33 +
34 +/**
35 + * REST resource for interacting with path calculations.
36 + */
37 +@Path("paths")
38 +public class PathsWebResource extends AbstractWebResource {
39 +
40 + // Host id format is 00:00:00:00:00:01/-1
41 + private static final int VLAN_SEPARATOR_OFFSET = 17;
42 + private static final int FIRST_MAC_ADDRESS_SEPARATOR_OFFSET = 2;
43 + private static final int SECOND_MAC_ADDRESS_SEPARATOR_OFFSET = 5;
44 + private static final int THIRD_MAC_ADDRESS_SEPARATOR_OFFSET = 8;
45 +
46 + /**
47 + * Determines if the id appears to be the id of a host.
48 + *
49 + * @param id id string
50 + * @return HostId if the id is valid, null otherwise
51 + */
52 + private HostId isHostId(String id) {
53 +
54 + if (id.length() < VLAN_SEPARATOR_OFFSET + 1 ||
55 + id.charAt(FIRST_MAC_ADDRESS_SEPARATOR_OFFSET) != ':' ||
56 + id.charAt(SECOND_MAC_ADDRESS_SEPARATOR_OFFSET) != ':' ||
57 + id.charAt(THIRD_MAC_ADDRESS_SEPARATOR_OFFSET) != ':' ||
58 + id.charAt(VLAN_SEPARATOR_OFFSET) != '/') {
59 + return null;
60 + }
61 +
62 + return HostId.hostId(id);
63 + }
64 +
65 + /**
66 + * Gets the paths between two elements.
67 + *
68 + * @param src source
69 + * @param dst destination
70 + * @return path data
71 + */
72 + @GET
73 + @Produces(MediaType.APPLICATION_JSON)
74 + @Path("{src}/{dst}")
75 + public Response getPath(@PathParam("src") String src,
76 + @PathParam("dst") String dst) {
77 + PathService pathService = get(PathService.class);
78 +
79 + ElementId srcElement = isHostId(src);
80 + ElementId dstElement = isHostId(dst);
81 +
82 + if (srcElement == null) {
83 + // Doesn't look like a host, assume it is a device
84 + srcElement = DeviceId.deviceId(src);
85 + }
86 +
87 + if (dstElement == null) {
88 + // Doesn't look like a host, assume it is a device
89 + dstElement = DeviceId.deviceId(dst);
90 + }
91 +
92 + Set<org.onosproject.net.Path> paths =
93 + pathService.getPaths(srcElement, dstElement);
94 + ObjectNode root =
95 + encodeArray(org.onosproject.net.Path.class, "paths", paths);
96 + return ok(root).build();
97 + }
98 +
99 +}
...@@ -43,7 +43,8 @@ ...@@ -43,7 +43,8 @@
43 org.onosproject.rest.IntentsWebResource, 43 org.onosproject.rest.IntentsWebResource,
44 org.onosproject.rest.FlowsWebResource, 44 org.onosproject.rest.FlowsWebResource,
45 org.onosproject.rest.TopologyWebResource, 45 org.onosproject.rest.TopologyWebResource,
46 - org.onosproject.rest.ConfigResource 46 + org.onosproject.rest.ConfigResource,
47 + org.onosproject.rest.PathsWebResource
47 </param-value> 48 </param-value>
48 </init-param> 49 </init-param>
49 <load-on-startup>1</load-on-startup> 50 <load-on-startup>1</load-on-startup>
......
1 +/*
2 + * Copyright 2015 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.io.UnsupportedEncodingException;
19 +import java.net.URLEncoder;
20 +import java.nio.charset.StandardCharsets;
21 +import java.util.Set;
22 +
23 +import org.hamcrest.Description;
24 +import org.hamcrest.TypeSafeDiagnosingMatcher;
25 +import org.junit.After;
26 +import org.junit.Before;
27 +import org.junit.Test;
28 +import org.onlab.osgi.ServiceDirectory;
29 +import org.onlab.osgi.TestServiceDirectory;
30 +import org.onlab.rest.BaseResource;
31 +import org.onosproject.codec.CodecService;
32 +import org.onosproject.codec.impl.CodecManager;
33 +import org.onosproject.net.ElementId;
34 +import org.onosproject.net.Link;
35 +import org.onosproject.net.Path;
36 +import org.onosproject.net.topology.PathService;
37 +
38 +import com.eclipsesource.json.JsonArray;
39 +import com.eclipsesource.json.JsonObject;
40 +import com.google.common.collect.ImmutableSet;
41 +import com.sun.jersey.api.client.WebResource;
42 +
43 +import static org.easymock.EasyMock.createMock;
44 +import static org.easymock.EasyMock.expect;
45 +import static org.easymock.EasyMock.replay;
46 +import static org.easymock.EasyMock.verify;
47 +import static org.hamcrest.Matchers.containsString;
48 +import static org.hamcrest.Matchers.hasSize;
49 +import static org.hamcrest.Matchers.is;
50 +import static org.hamcrest.Matchers.notNullValue;
51 +import static org.junit.Assert.assertThat;
52 +import static org.onosproject.net.NetTestTools.createPath;
53 +import static org.onosproject.net.NetTestTools.did;
54 +import static org.onosproject.net.NetTestTools.hid;
55 +
56 +/**
57 + * Unit tests for paths REST APIs.
58 + */
59 +public class PathsResourceTest extends ResourceTest {
60 + Path path1 = createPath("dev1", "dev2");
61 + Path path2 = createPath("dev2", "dev3");
62 + Set<Path> paths = ImmutableSet.of(path1, path2);
63 +
64 + final PathService mockPathService = createMock(PathService.class);
65 +
66 + /**
67 + * Hamcrest matcher for a path and its JSON representation.
68 + */
69 + private final class PathJsonMatcher extends TypeSafeDiagnosingMatcher<JsonObject> {
70 +
71 + private final Path path;
72 +
73 + /**
74 + * Creates the matcher.
75 + *
76 + * @param pathValue the path object to match
77 + */
78 + private PathJsonMatcher(Path pathValue) {
79 + path = pathValue;
80 + }
81 +
82 + @Override
83 + public boolean matchesSafely(JsonObject pathJson, Description description) {
84 +
85 + double jsonCost = pathJson.get("cost").asDouble();
86 + if (jsonCost != path.cost()) {
87 + description.appendText("src device was " + jsonCost);
88 + return false;
89 + }
90 +
91 + JsonArray jsonLinks = pathJson.get("links").asArray();
92 + assertThat(jsonLinks.size(), is(path.links().size()));
93 +
94 + for (int linkIndex = 0; linkIndex < jsonLinks.size(); linkIndex++) {
95 + Link link = path.links().get(linkIndex);
96 + JsonObject jsonLink = jsonLinks.get(0).asObject();
97 +
98 + JsonObject jsonLinkSrc = jsonLink.get("src").asObject();
99 + String srcDevice = jsonLinkSrc.get("device").asString();
100 + if (!srcDevice.equals(link.src().deviceId().toString())) {
101 + description.appendText("src device was " + jsonLinkSrc);
102 + return false;
103 + }
104 +
105 + JsonObject jsonLinkDst = jsonLink.get("dst").asObject();
106 + String dstDevice = jsonLinkDst.get("device").asString();
107 + if (!dstDevice.equals(link.dst().deviceId().toString())) {
108 + description.appendText("dst device was " + jsonLinkDst);
109 + return false;
110 + }
111 + }
112 +
113 + return true;
114 + }
115 +
116 + @Override
117 + public void describeTo(Description description) {
118 + description.appendText(path.toString());
119 + }
120 + }
121 +
122 + /**
123 + * Factory to allocate an connect point matcher.
124 + *
125 + * @param path path object we are looking for
126 + * @return matcher
127 + */
128 + private PathJsonMatcher matchesPath(Path path) {
129 + return new PathJsonMatcher(path);
130 + }
131 +
132 + @Before
133 + public void setUp() {
134 +
135 + // Register the services needed for the test
136 + CodecManager codecService = new CodecManager();
137 + codecService.activate();
138 + ServiceDirectory testDirectory =
139 + new TestServiceDirectory()
140 + .add(PathService.class, mockPathService)
141 + .add(CodecService.class, codecService);
142 +
143 + BaseResource.setServiceDirectory(testDirectory);
144 + }
145 +
146 + @After
147 + public void tearDown() throws Exception {
148 + super.tearDown();
149 + verify(mockPathService);
150 + }
151 +
152 + /**
153 + * Tests a REST path GET for the given endpoints.
154 + *
155 + * @param srcElement source element of the path
156 + * @param dstElement destination element of the path
157 + *
158 + * @throws UnsupportedEncodingException
159 + */
160 + private void runTest(ElementId srcElement, ElementId dstElement)
161 + throws UnsupportedEncodingException {
162 + expect(mockPathService.getPaths(srcElement, dstElement))
163 + .andReturn(paths)
164 + .once();
165 + replay(mockPathService);
166 +
167 + String srcId = URLEncoder.encode(srcElement.toString(),
168 + StandardCharsets.UTF_8.name());
169 + String dstId = URLEncoder.encode(dstElement.toString(),
170 + StandardCharsets.UTF_8.name());
171 +
172 + String url = "paths/" + srcId + "/" + dstId;
173 + WebResource rs = resource();
174 + String response = rs.path(url).get(String.class);
175 + assertThat(response, containsString("{\"paths\":["));
176 +
177 + JsonObject result = JsonObject.readFrom(response);
178 + assertThat(result, notNullValue());
179 +
180 + assertThat(result.names(), hasSize(1));
181 + assertThat(result.names().get(0), is("paths"));
182 +
183 + JsonArray jsonPaths = result.get("paths").asArray();
184 + assertThat(jsonPaths, notNullValue());
185 + assertThat(jsonPaths.size(), is(2));
186 +
187 + JsonObject path1Json = jsonPaths.get(0).asObject();
188 + assertThat(path1Json, matchesPath(path1));
189 +
190 + JsonObject path2Json = jsonPaths.get(1).asObject();
191 + assertThat(path2Json, matchesPath(path2));
192 + }
193 +
194 + /**
195 + * Tests a path between two hosts.
196 + *
197 + * @throws UnsupportedEncodingException if UTF-8 not found
198 + */
199 + @Test
200 + public void hostToHost() throws UnsupportedEncodingException {
201 + runTest(hid("01:23:45:67:89:AB/2"), hid("AB:89:67:45:23:01/4"));
202 + }
203 +
204 + /**
205 + * Tests a path with a host as the source and a switch as the destination.
206 + *
207 + * @throws UnsupportedEncodingException if UTF-8 not found
208 + */
209 + @Test
210 + public void hostToDevice() throws UnsupportedEncodingException {
211 + runTest(hid("01:23:45:67:89:AB/2"), did("switch1"));
212 + }
213 +
214 + /**
215 + * Tests a path with a switch as the source and a host as the destination.
216 + *
217 + * @throws UnsupportedEncodingException if UTF-8 not found
218 + */
219 + @Test
220 + public void deviceToHost() throws UnsupportedEncodingException {
221 + runTest(did("switch1"), hid("01:23:45:67:89:AB/2"));
222 + }
223 +
224 + /**
225 + * Tests a path between two switches.
226 + *
227 + * @throws UnsupportedEncodingException if UTF-8 not found
228 + */
229 + @Test
230 + public void deviceToDevice() throws UnsupportedEncodingException {
231 + runTest(did("switch1"), did("switch2"));
232 + }
233 +}