Ray Milkey

Implement REST API for Hosts objects

Change-Id: Ie1d4f7c19c47d2d357dedf81016e4d4d197e4052
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
38 <version>${project.version}</version> 38 <version>${project.version}</version>
39 <scope>test</scope> 39 <scope>test</scope>
40 </dependency> 40 </dependency>
41 + <dependency>
42 + <groupId>org.easymock</groupId>
43 + <artifactId>easymock</artifactId>
44 + </dependency>
41 </dependencies> 45 </dependencies>
42 46
43 <properties> 47 <properties>
......
...@@ -25,6 +25,8 @@ import org.onosproject.codec.JsonCodec; ...@@ -25,6 +25,8 @@ import org.onosproject.codec.JsonCodec;
25 import org.onosproject.net.Annotations; 25 import org.onosproject.net.Annotations;
26 import org.onosproject.net.ConnectPoint; 26 import org.onosproject.net.ConnectPoint;
27 import org.onosproject.net.Device; 27 import org.onosproject.net.Device;
28 +import org.onosproject.net.Host;
29 +import org.onosproject.net.HostLocation;
28 import org.onosproject.net.Link; 30 import org.onosproject.net.Link;
29 import org.onosproject.net.Port; 31 import org.onosproject.net.Port;
30 import org.slf4j.Logger; 32 import org.slf4j.Logger;
...@@ -53,11 +55,13 @@ public class CodecManager implements CodecService { ...@@ -53,11 +55,13 @@ public class CodecManager implements CodecService {
53 registerCodec(Port.class, new PortCodec()); 55 registerCodec(Port.class, new PortCodec());
54 registerCodec(ConnectPoint.class, new ConnectPointCodec()); 56 registerCodec(ConnectPoint.class, new ConnectPointCodec());
55 registerCodec(Link.class, new LinkCodec()); 57 registerCodec(Link.class, new LinkCodec());
58 + registerCodec(Host.class, new HostCodec());
59 + registerCodec(HostLocation.class, new HostLocationCodec());
56 log.info("Started"); 60 log.info("Started");
57 } 61 }
58 62
59 @Deactivate 63 @Deactivate
60 - public void deativate() { 64 + public void deactivate() {
61 codecs.clear(); 65 codecs.clear();
62 log.info("Stopped"); 66 log.info("Stopped");
63 } 67 }
......
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.onlab.packet.IpAddress;
19 +import org.onosproject.codec.CodecContext;
20 +import org.onosproject.codec.JsonCodec;
21 +import org.onosproject.net.Host;
22 +import org.onosproject.net.HostLocation;
23 +
24 +import com.fasterxml.jackson.databind.node.ArrayNode;
25 +import com.fasterxml.jackson.databind.node.ObjectNode;
26 +
27 +import static com.google.common.base.Preconditions.checkNotNull;
28 +
29 +/**
30 + * Host JSON codec.
31 + */
32 +public class HostCodec extends AnnotatedCodec<Host> {
33 +
34 + @Override
35 + public ObjectNode encode(Host host, CodecContext context) {
36 + checkNotNull(host, "Host cannot be null");
37 + final JsonCodec<HostLocation> locationCodec = new HostLocationCodec();
38 + final ObjectNode result = context.mapper().createObjectNode()
39 + .put("id", host.id().toString())
40 + .put("mac", host.mac().toString())
41 + .put("vlan", host.vlan().toString());
42 +
43 + final ArrayNode jsonIpAddresses = result.putArray("ipAddresses");
44 + for (final IpAddress ipAddress : host.ipAddresses()) {
45 + jsonIpAddresses.add(ipAddress.toString());
46 + }
47 + result.set("ipAddresses", jsonIpAddresses);
48 + result.set("location", locationCodec.encode(host.location(), context));
49 +
50 + return annotate(result, host, context);
51 + }
52 +
53 +}
54 +
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.HostLocation;
21 +
22 +import com.fasterxml.jackson.databind.node.ObjectNode;
23 +
24 +import static com.google.common.base.Preconditions.checkNotNull;
25 +
26 +/**
27 + * Host JSON codec.
28 + */
29 +public class HostLocationCodec extends JsonCodec<HostLocation> {
30 +
31 + @Override
32 + public ObjectNode encode(HostLocation hostLocation, CodecContext context) {
33 + checkNotNull(hostLocation, "Host location cannot be null");
34 + return context.mapper().createObjectNode()
35 + .put("elementId", hostLocation.elementId().toString())
36 + .put("port", hostLocation.port().toString());
37 + }
38 +
39 +}
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.Host;
26 +import org.onosproject.net.host.HostService;
27 +
28 +import com.fasterxml.jackson.databind.node.ObjectNode;
29 +
30 +import static org.onosproject.net.HostId.hostId;
31 +
32 +/**
33 + * REST resource for interacting with the inventory of hosts.
34 + */
35 +@Path("hosts")
36 +public class HostsWebResource extends AbstractWebResource {
37 +
38 + public static final String HOST_NOT_FOUND = "Host is not found";
39 +
40 + @GET
41 + @Produces(MediaType.APPLICATION_JSON)
42 + public Response getHosts() {
43 + final Iterable<Host> hosts = get(HostService.class).getHosts();
44 + final ObjectNode root = encodeArray(Host.class, "hosts", hosts);
45 + return ok(root.toString()).build();
46 + }
47 +
48 + @GET
49 + @Produces(MediaType.APPLICATION_JSON)
50 + @Path("{id}")
51 + public Response getHostById(@PathParam("id") String id) {
52 + final Host host = nullIsNotFound(get(HostService.class).getHost(hostId(id)),
53 + HOST_NOT_FOUND);
54 + final ObjectNode root = codec(Host.class).encode(host, this);
55 + return ok(root.toString()).build();
56 + }
57 +
58 + @GET
59 + @Produces(MediaType.APPLICATION_JSON)
60 + @Path("{mac}/{vlan}")
61 + public Response getHostByMacAndVlan(@PathParam("mac") String mac,
62 + @PathParam("vlan") String vlan) {
63 + final Host host = nullIsNotFound(get(HostService.class).getHost(hostId(mac + "/" + vlan)),
64 + HOST_NOT_FOUND);
65 + final ObjectNode root = codec(Host.class).encode(host, this);
66 + return ok(root.toString()).build();
67 + }
68 +}
69 +
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
37 37
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.ConfigResource 41 org.onosproject.rest.ConfigResource
41 </param-value> 42 </param-value>
42 </init-param> 43 </init-param>
......
...@@ -45,7 +45,7 @@ public class GreetResourceTest extends JerseyTest { ...@@ -45,7 +45,7 @@ public class GreetResourceTest extends JerseyTest {
45 public void basics() { 45 public void basics() {
46 WebResource rs = resource(); 46 WebResource rs = resource();
47 String response = rs.path("greet").get(String.class); 47 String response = rs.path("greet").get(String.class);
48 - assertTrue("incorrect response", response.contains("Whazup ")); 48 + assertTrue("incorrect response", response.contains("greeting"));
49 } 49 }
50 50
51 } 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 +
19 +import java.util.HashSet;
20 +import java.util.Set;
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.packet.IpAddress;
30 +import org.onlab.packet.MacAddress;
31 +import org.onlab.rest.BaseResource;
32 +import org.onosproject.codec.CodecService;
33 +import org.onosproject.codec.impl.CodecManager;
34 +import org.onosproject.net.DefaultHost;
35 +import org.onosproject.net.DeviceId;
36 +import org.onosproject.net.Host;
37 +import org.onosproject.net.HostId;
38 +import org.onosproject.net.HostLocation;
39 +import org.onosproject.net.host.HostService;
40 +import org.onosproject.net.provider.ProviderId;
41 +
42 +import com.eclipsesource.json.JsonArray;
43 +import com.eclipsesource.json.JsonObject;
44 +import com.google.common.collect.ImmutableSet;
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.onlab.packet.MacAddress.valueOf;
58 +import static org.onlab.packet.VlanId.vlanId;
59 +import static org.onosproject.net.PortNumber.portNumber;
60 +
61 +/**
62 + * Simple example on how to write a JAX-RS unit test using Jersey test framework.
63 + * A base class should/will be created to provide further assistance for testing.
64 + */
65 +public class HostResourceTest extends JerseyTest {
66 + final HostService mockHostService = createMock(HostService.class);
67 + final HashSet<Host> hosts = new HashSet<>();
68 +
69 + public HostResourceTest() {
70 + super("org.onosproject.rest");
71 + }
72 +
73 + @Before
74 + public void setUp() {
75 + expect(mockHostService.getHosts()).andReturn(hosts).anyTimes();
76 +
77 + // Register the services needed for the test
78 + final CodecManager codecService = new CodecManager();
79 + codecService.activate();
80 + ServiceDirectory testDirectory =
81 + new TestServiceDirectory()
82 + .add(HostService.class, mockHostService)
83 + .add(CodecService.class, codecService);
84 +
85 + BaseResource.setServiceDirectory(testDirectory);
86 + }
87 +
88 + @After
89 + public void tearDown() throws Exception {
90 + super.tearDown();
91 + verify(mockHostService);
92 + }
93 +
94 + /**
95 + * Hamcrest matcher to check that a host representation in JSON matches
96 + * the actual host.
97 + */
98 + public static class HostJsonMatcher extends TypeSafeMatcher<JsonObject> {
99 + private final Host host;
100 + private String reason = "";
101 +
102 + public HostJsonMatcher(Host hostValue) {
103 + host = hostValue;
104 + }
105 +
106 + @Override
107 + public boolean matchesSafely(JsonObject jsonHost) {
108 + // Check id
109 + final String jsonId = jsonHost.get("id").asString();
110 + if (!jsonId.equals(host.id().toString())) {
111 + reason = "id " + host.id().toString();
112 + return false;
113 + }
114 +
115 + // Check vlan id
116 + final String jsonVlanId = jsonHost.get("vlan").asString();
117 + if (!jsonVlanId.equals(host.vlan().toString())) {
118 + reason = "vlan id " + host.vlan().toString();
119 + return false;
120 + }
121 +
122 + // Check mac address
123 + final String jsonMacAddress = jsonHost.get("mac").asString();
124 + if (!jsonMacAddress.equals(host.mac().toString())) {
125 + reason = "mac address " + host.mac().toString();
126 + return false;
127 + }
128 +
129 + // Check location element id
130 + final JsonObject jsonLocation = jsonHost.get("location").asObject();
131 + final String jsonLocationElementId = jsonLocation.get("elementId").asString();
132 + if (!jsonLocationElementId.equals(host.location().elementId().toString())) {
133 + reason = "location element id " + host.location().elementId().toString();
134 + return false;
135 + }
136 +
137 + // Check location port number
138 + final String jsonLocationPortNumber = jsonLocation.get("port").asString();
139 + if (!jsonLocationPortNumber.equals(host.location().port().toString())) {
140 + reason = "location portNumber " + host.location().port().toString();
141 + return false;
142 + }
143 +
144 + // Check Ip Addresses
145 + final JsonArray jsonHostIps = jsonHost.get("ipAddresses").asArray();
146 + final Set<IpAddress> expectedHostIps = host.ipAddresses();
147 + if (jsonHostIps.size() != expectedHostIps.size()) {
148 + reason = "IP address arrays differ in size";
149 + return false;
150 + }
151 +
152 + return true;
153 + }
154 +
155 + @Override
156 + public void describeTo(Description description) {
157 + description.appendText(reason);
158 + }
159 + }
160 +
161 + /**
162 + * Factory to allocate a host array matcher.
163 + *
164 + * @param host host object we are looking for
165 + * @return matcher
166 + */
167 + private static HostJsonMatcher matchesHost(Host host) {
168 + return new HostJsonMatcher(host);
169 + }
170 +
171 + /**
172 + * Hamcrest matcher to check that a host is represented properly in a JSON
173 + * array of hosts.
174 + */
175 + public static class HostJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
176 + private final Host host;
177 + private String reason = "";
178 +
179 + public HostJsonArrayMatcher(Host hostValue) {
180 + host = hostValue;
181 + }
182 +
183 + @Override
184 + public boolean matchesSafely(JsonArray json) {
185 + boolean hostFound = false;
186 + final int expectedAttributes = 5;
187 + for (int jsonHostIndex = 0; jsonHostIndex < json.size();
188 + jsonHostIndex++) {
189 +
190 + final JsonObject jsonHost = json.get(jsonHostIndex).asObject();
191 +
192 + if (jsonHost.names().size() != expectedAttributes) {
193 + reason = "Found a host with the wrong number of attributes";
194 + return false;
195 + }
196 +
197 + final String jsonHostId = jsonHost.get("id").asString();
198 + if (jsonHostId.equals(host.id().toString())) {
199 + hostFound = true;
200 +
201 + // We found the correct host, check attribute values
202 + assertThat(jsonHost, matchesHost(host));
203 + }
204 + }
205 + if (!hostFound) {
206 + reason = "Host with id " + host.id().toString() + " not found";
207 + return false;
208 + } else {
209 + return true;
210 + }
211 + }
212 +
213 + @Override
214 + public void describeTo(Description description) {
215 + description.appendText(reason);
216 + }
217 + }
218 +
219 + /**
220 + * Factory to allocate a host array matcher.
221 + *
222 + * @param host host object we are looking for
223 + * @return matcher
224 + */
225 + private static HostJsonArrayMatcher hasHost(Host host) {
226 + return new HostJsonArrayMatcher(host);
227 + }
228 +
229 + /**
230 + * Tests the result of the rest api GET when there are no hosts.
231 + */
232 + @Test
233 + public void testHostsEmptyArray() {
234 + replay(mockHostService);
235 + WebResource rs = resource();
236 + String response = rs.path("hosts").get(String.class);
237 + assertThat(response, is("{\"hosts\":[]}"));
238 + }
239 +
240 + /**
241 + * Tests the result of the rest api GET when hosts are defined.
242 + */
243 + @Test
244 + public void testHostsArray() {
245 + replay(mockHostService);
246 + final ProviderId pid = new ProviderId("of", "foo");
247 + final MacAddress mac1 = MacAddress.valueOf("00:00:11:00:00:01");
248 + final Set<IpAddress> ips1 = ImmutableSet.of(IpAddress.valueOf("1111:1111:1111:1::"));
249 + final Host host1 =
250 + new DefaultHost(pid, HostId.hostId(mac1), valueOf(1), vlanId((short) 1),
251 + new HostLocation(DeviceId.deviceId("1"), portNumber(11), 1),
252 + ips1);
253 + final MacAddress mac2 = MacAddress.valueOf("00:00:11:00:00:02");
254 + final Set<IpAddress> ips2 = ImmutableSet.of(
255 + IpAddress.valueOf("2222:2222:2222:1::"),
256 + IpAddress.valueOf("2222:2222:2222:2::"));
257 + final Host host2 =
258 + new DefaultHost(pid, HostId.hostId(mac2), valueOf(2), vlanId((short) 2),
259 + new HostLocation(DeviceId.deviceId("2"), portNumber(22), 2),
260 + ips2);
261 + hosts.add(host1);
262 + hosts.add(host2);
263 + WebResource rs = resource();
264 + String response = rs.path("hosts").get(String.class);
265 + assertThat(response, containsString("{\"hosts\":["));
266 +
267 + final JsonObject result = JsonObject.readFrom(response);
268 + assertThat(result, notNullValue());
269 +
270 + assertThat(result.names(), hasSize(1));
271 + assertThat(result.names().get(0), is("hosts"));
272 +
273 + final JsonArray hosts = result.get("hosts").asArray();
274 + assertThat(hosts, notNullValue());
275 +
276 + assertThat(hosts, hasHost(host1));
277 + assertThat(hosts, hasHost(host2));
278 + }
279 +
280 + /**
281 + * Tests fetch of one host by Id.
282 + */
283 + @Test
284 + public void testSingleHostByIdFetch() {
285 + final ProviderId pid = new ProviderId("of", "foo");
286 + final MacAddress mac1 = MacAddress.valueOf("00:00:11:00:00:01");
287 + final Set<IpAddress> ips1 = ImmutableSet.of(IpAddress.valueOf("1111:1111:1111:1::"));
288 + final Host host1 =
289 + new DefaultHost(pid, HostId.hostId(mac1), valueOf(1), vlanId((short) 1),
290 + new HostLocation(DeviceId.deviceId("1"), portNumber(11), 1),
291 + ips1);
292 +
293 + hosts.add(host1);
294 +
295 + expect(mockHostService.getHost(HostId.hostId("00:00:11:00:00:01/1")))
296 + .andReturn(host1)
297 + .anyTimes();
298 + replay(mockHostService);
299 +
300 + WebResource rs = resource();
301 + String response = rs.path("hosts/00:00:11:00:00:01%2F1").get(String.class);
302 + final JsonObject result = JsonObject.readFrom(response);
303 + assertThat(result, matchesHost(host1));
304 + }
305 +
306 + /**
307 + * Tests fetch of one host by mac and vlan.
308 + */
309 + @Test
310 + public void testSingleHostByMacAndVlanFetch() {
311 + final ProviderId pid = new ProviderId("of", "foo");
312 + final MacAddress mac1 = MacAddress.valueOf("00:00:11:00:00:01");
313 + final Set<IpAddress> ips1 = ImmutableSet.of(IpAddress.valueOf("1111:1111:1111:1::"));
314 + final Host host1 =
315 + new DefaultHost(pid, HostId.hostId(mac1), valueOf(1), vlanId((short) 1),
316 + new HostLocation(DeviceId.deviceId("1"), portNumber(11), 1),
317 + ips1);
318 +
319 + hosts.add(host1);
320 +
321 + expect(mockHostService.getHost(HostId.hostId("00:00:11:00:00:01/1")))
322 + .andReturn(host1)
323 + .anyTimes();
324 + replay(mockHostService);
325 +
326 + WebResource rs = resource();
327 + String response = rs.path("hosts/00:00:11:00:00:01/1").get(String.class);
328 + final JsonObject result = JsonObject.readFrom(response);
329 + assertThat(result, matchesHost(host1));
330 + }
331 +
332 +}
333 +