Ray Milkey

Implement REST APIs for flows:

- get all flows
- get all flows for a device
- get all flows for a device with a given ID

Change-Id: Ifb1541e4ae4a7e49f1347b34bef2fe788902368d
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
41 <dependency> 41 <dependency>
42 <groupId>org.easymock</groupId> 42 <groupId>org.easymock</groupId>
43 <artifactId>easymock</artifactId> 43 <artifactId>easymock</artifactId>
44 + <scope>test</scope>
44 </dependency> 45 </dependency>
45 </dependencies> 46 </dependencies>
46 47
......
...@@ -29,6 +29,9 @@ import org.onosproject.net.Host; ...@@ -29,6 +29,9 @@ 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.flow.FlowEntry;
33 +import org.onosproject.net.flow.TrafficSelector;
34 +import org.onosproject.net.flow.TrafficTreatment;
32 import org.onosproject.net.intent.Intent; 35 import org.onosproject.net.intent.Intent;
33 import org.slf4j.Logger; 36 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory; 37 import org.slf4j.LoggerFactory;
...@@ -59,6 +62,9 @@ public class CodecManager implements CodecService { ...@@ -59,6 +62,9 @@ public class CodecManager implements CodecService {
59 registerCodec(Host.class, new HostCodec()); 62 registerCodec(Host.class, new HostCodec());
60 registerCodec(HostLocation.class, new HostLocationCodec()); 63 registerCodec(HostLocation.class, new HostLocationCodec());
61 registerCodec(Intent.class, new IntentCodec()); 64 registerCodec(Intent.class, new IntentCodec());
65 + registerCodec(FlowEntry.class, new FlowEntryCodec());
66 + registerCodec(TrafficTreatment.class, new TrafficTreatmentCodec());
67 + registerCodec(TrafficSelector.class, new TrafficSelectorCodec());
62 log.info("Started"); 68 log.info("Started");
63 } 69 }
64 70
......
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.flow.FlowEntry;
21 +import org.onosproject.net.flow.TrafficSelector;
22 +import org.onosproject.net.flow.TrafficTreatment;
23 +
24 +import com.fasterxml.jackson.databind.node.ObjectNode;
25 +
26 +import static com.google.common.base.Preconditions.checkNotNull;
27 +
28 +/**
29 + * Flow entry JSON codec.
30 + */
31 +public class FlowEntryCodec extends JsonCodec<FlowEntry> {
32 +
33 + @Override
34 + public ObjectNode encode(FlowEntry flowEntry, CodecContext context) {
35 + checkNotNull(flowEntry, "Flow entry cannot be null");
36 +
37 + final ObjectNode result = context.mapper().createObjectNode()
38 + .put("id", Long.toString(flowEntry.id().value()))
39 + .put("appId", flowEntry.appId())
40 + .put("groupId", flowEntry.groupId().id())
41 + .put("priority", flowEntry.priority())
42 + .put("timeout", flowEntry.timeout())
43 + .put("isPermanent", flowEntry.isPermanent())
44 + .put("deviceId", flowEntry.deviceId().toString())
45 + .put("state", flowEntry.state().toString())
46 + .put("life", flowEntry.life())
47 + .put("packets", flowEntry.packets())
48 + .put("bytes", flowEntry.bytes())
49 + .put("lastSeen", flowEntry.lastSeen());
50 +
51 + if (flowEntry.treatment() != null) {
52 + final JsonCodec<TrafficTreatment> treatmentCodec =
53 + new TrafficTreatmentCodec();
54 + result.set("treatment", treatmentCodec.encode(flowEntry.treatment(), context));
55 + }
56 +
57 + if (flowEntry.selector() != null) {
58 + final JsonCodec<TrafficSelector> selectorCodec =
59 + new TrafficSelectorCodec();
60 + result.set("selector", selectorCodec.encode(flowEntry.selector(), context));
61 + }
62 +
63 + return result;
64 + }
65 +
66 +}
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.onosproject.codec.CodecContext;
19 +import org.onosproject.codec.JsonCodec;
20 +import org.onosproject.net.flow.TrafficSelector;
21 +import org.onosproject.net.flow.criteria.Criterion;
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 + * Traffic selector codec.
30 + */
31 +public class TrafficSelectorCodec extends JsonCodec<TrafficSelector> {
32 + @Override
33 + public ObjectNode encode(TrafficSelector selector, CodecContext context) {
34 + checkNotNull(selector, "Traffic selector cannot be null");
35 +
36 + final ObjectNode result = context.mapper().createObjectNode();
37 + final ArrayNode jsonCriteria = result.putArray("criteria");
38 +
39 + if (selector.criteria() != null) {
40 + for (final Criterion criterion :selector.criteria()) {
41 + // TODO: would be better to have a codec that understands criteria
42 + jsonCriteria.add(criterion.toString());
43 + }
44 + }
45 +
46 + return result;
47 + }
48 +}
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.flow.TrafficTreatment;
21 +import org.onosproject.net.flow.instructions.Instruction;
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 + * Traffic treatment codec.
30 + */
31 +public class TrafficTreatmentCodec extends JsonCodec<TrafficTreatment> {
32 + @Override
33 + public ObjectNode encode(TrafficTreatment treatment, CodecContext context) {
34 + checkNotNull(treatment, "Traffic treatment cannot be null");
35 +
36 + final ObjectNode result = context.mapper().createObjectNode();
37 + final ArrayNode jsonInstructions = result.putArray("instructions");
38 +
39 + if (treatment.instructions() != null) {
40 + for (final Instruction instruction : treatment.instructions()) {
41 + // TODO: would be better to have a codec that understands instructions
42 + jsonInstructions.add(instruction.toString());
43 + }
44 + }
45 +
46 + return result;
47 + }
48 +}
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.onlab.util.ItemNotFoundException;
26 +import org.onosproject.codec.impl.FlowEntryCodec;
27 +import org.onosproject.net.Device;
28 +import org.onosproject.net.DeviceId;
29 +import org.onosproject.net.device.DeviceService;
30 +import org.onosproject.net.flow.FlowEntry;
31 +import org.onosproject.net.flow.FlowRuleService;
32 +
33 +import com.fasterxml.jackson.databind.node.ArrayNode;
34 +import com.fasterxml.jackson.databind.node.ObjectNode;
35 +
36 +/**
37 + * REST resource for interacting with the inventory of flows.
38 + */
39 +
40 +@Path("flows")
41 +public class FlowsWebResource extends AbstractWebResource {
42 + public static final String DEVICE_NOT_FOUND = "Device is not found";
43 +
44 + final FlowRuleService service = get(FlowRuleService.class);
45 + final ObjectNode root = mapper().createObjectNode();
46 + final ArrayNode flowsNode = root.putArray("flows");
47 + final FlowEntryCodec flowEntryCodec = new FlowEntryCodec();
48 +
49 + /**
50 + * Gets an array containing all the intents in the system.
51 + *
52 + * @return array of all the intents in the system
53 + */
54 + @GET
55 + @Produces(MediaType.APPLICATION_JSON)
56 + public Response getFlows() {
57 +
58 + final Iterable<Device> devices = get(DeviceService.class).getDevices();
59 + for (final Device device : devices) {
60 + final Iterable<FlowEntry> deviceEntries = service.getFlowEntries(device.id());
61 + if (deviceEntries != null) {
62 + for (final FlowEntry entry : deviceEntries) {
63 + flowsNode.add(flowEntryCodec.encode(entry, this));
64 + }
65 + }
66 + }
67 +
68 + return ok(root.toString()).build();
69 + }
70 +
71 + /**
72 + * Gets the flows for a device, where the device is specified by Id.
73 + *
74 + * @param deviceId Id of device to look up
75 + * @return flow data as an array
76 + */
77 + @GET
78 + @Produces(MediaType.APPLICATION_JSON)
79 + @Path("{deviceId}")
80 + public Response getFlowByDeviceId(@PathParam("deviceId") String deviceId) {
81 + final Iterable<FlowEntry> deviceEntries =
82 + service.getFlowEntries(DeviceId.deviceId(deviceId));
83 +
84 + if (!deviceEntries.iterator().hasNext()) {
85 + throw new ItemNotFoundException(DEVICE_NOT_FOUND);
86 + }
87 + for (final FlowEntry entry : deviceEntries) {
88 + flowsNode.add(flowEntryCodec.encode(entry, this));
89 + }
90 + return ok(root.toString()).build();
91 + }
92 +
93 + /**
94 + * Gets the flows for a device, where the device is specified by Id.
95 + *
96 + * @param deviceId Id of device to look up
97 + * @param flowId Id of flow to look up
98 + * @return flow data as an array
99 + */
100 + @GET
101 + @Produces(MediaType.APPLICATION_JSON)
102 + @Path("{deviceId}/{flowId}")
103 + public Response getFlowByDeviceIdAndFlowId(@PathParam("deviceId") String deviceId,
104 + @PathParam("flowId") long flowId) {
105 + final Iterable<FlowEntry> deviceEntries =
106 + service.getFlowEntries(DeviceId.deviceId(deviceId));
107 +
108 + if (!deviceEntries.iterator().hasNext()) {
109 + throw new ItemNotFoundException(DEVICE_NOT_FOUND);
110 + }
111 + for (final FlowEntry entry : deviceEntries) {
112 + if (entry.id().value() == flowId) {
113 + flowsNode.add(flowEntryCodec.encode(entry, this));
114 + }
115 + }
116 + return ok(root.toString()).build();
117 + }
118 +}
...@@ -58,7 +58,7 @@ public class IntentsWebResource extends AbstractWebResource { ...@@ -58,7 +58,7 @@ public class IntentsWebResource extends AbstractWebResource {
58 @GET 58 @GET
59 @Produces(MediaType.APPLICATION_JSON) 59 @Produces(MediaType.APPLICATION_JSON)
60 @Path("{id}") 60 @Path("{id}")
61 - public Response getHostById(@PathParam("id") long id) { 61 + public Response getIntentById(@PathParam("id") long id) {
62 final Intent intent = nullIsNotFound(get(IntentService.class) 62 final Intent intent = nullIsNotFound(get(IntentService.class)
63 .getIntent(IntentId.valueOf(id)), 63 .getIntent(IntentId.valueOf(id)),
64 INTENT_NOT_FOUND); 64 INTENT_NOT_FOUND);
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
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.IntentsWebResource,
42 + org.onosproject.rest.FlowsWebResource,
42 org.onosproject.rest.ConfigResource 43 org.onosproject.rest.ConfigResource
43 </param-value> 44 </param-value>
44 </init-param> 45 </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.HashMap;
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.MacAddress;
30 +import org.onlab.rest.BaseResource;
31 +import org.onosproject.codec.CodecService;
32 +import org.onosproject.codec.impl.CodecManager;
33 +import org.onosproject.core.DefaultGroupId;
34 +import org.onosproject.core.GroupId;
35 +import org.onosproject.net.DefaultDevice;
36 +import org.onosproject.net.Device;
37 +import org.onosproject.net.DeviceId;
38 +import org.onosproject.net.device.DeviceService;
39 +import org.onosproject.net.flow.DefaultTrafficSelector;
40 +import org.onosproject.net.flow.DefaultTrafficTreatment;
41 +import org.onosproject.net.flow.FlowEntry;
42 +import org.onosproject.net.flow.FlowId;
43 +import org.onosproject.net.flow.FlowRuleService;
44 +import org.onosproject.net.flow.TrafficSelector;
45 +import org.onosproject.net.flow.TrafficTreatment;
46 +import org.onosproject.net.flow.criteria.Criterion;
47 +import org.onosproject.net.flow.instructions.Instruction;
48 +import org.onosproject.net.flow.instructions.L0ModificationInstruction;
49 +
50 +import com.eclipsesource.json.JsonArray;
51 +import com.eclipsesource.json.JsonObject;
52 +import com.eclipsesource.json.JsonValue;
53 +import com.google.common.collect.ImmutableSet;
54 +import com.sun.jersey.api.client.UniformInterfaceException;
55 +import com.sun.jersey.api.client.WebResource;
56 +import com.sun.jersey.test.framework.JerseyTest;
57 +
58 +import static org.easymock.EasyMock.anyObject;
59 +import static org.easymock.EasyMock.createMock;
60 +import static org.easymock.EasyMock.expect;
61 +import static org.easymock.EasyMock.replay;
62 +import static org.easymock.EasyMock.verify;
63 +import static org.hamcrest.Matchers.containsString;
64 +import static org.hamcrest.Matchers.hasSize;
65 +import static org.hamcrest.Matchers.is;
66 +import static org.hamcrest.Matchers.not;
67 +import static org.hamcrest.Matchers.notNullValue;
68 +import static org.junit.Assert.assertThat;
69 +import static org.junit.Assert.fail;
70 +
71 +/**
72 + * Unit tests for Flows REST APIs.
73 + */
74 +public class FlowsResourceTest extends JerseyTest {
75 + final FlowRuleService mockFlowService = createMock(FlowRuleService.class);
76 + final HashMap<DeviceId, Set<FlowEntry>> rules = new HashMap<>();
77 +
78 + final DeviceService mockDeviceService = createMock(DeviceService.class);
79 +
80 + final DeviceId deviceId1 = DeviceId.deviceId("1");
81 + final DeviceId deviceId2 = DeviceId.deviceId("2");
82 + final DeviceId deviceId3 = DeviceId.deviceId("3");
83 + final Device device1 = new DefaultDevice(null, deviceId1, Device.Type.OTHER,
84 + "", "", "", "", null);
85 + final Device device2 = new DefaultDevice(null, deviceId2, Device.Type.OTHER,
86 + "", "", "", "", null);
87 +
88 + final MockFlowEntry flow1 = new MockFlowEntry(deviceId1, 1);
89 + final MockFlowEntry flow2 = new MockFlowEntry(deviceId1, 2);
90 +
91 + final MockFlowEntry flow3 = new MockFlowEntry(deviceId2, 3);
92 + final MockFlowEntry flow4 = new MockFlowEntry(deviceId2, 4);
93 +
94 + final MockFlowEntry flow5 = new MockFlowEntry(deviceId2, 5);
95 + final MockFlowEntry flow6 = new MockFlowEntry(deviceId2, 6);
96 +
97 + /**
98 + * Mock class for a flow entry.
99 + */
100 + private static class MockFlowEntry implements FlowEntry {
101 + final DeviceId deviceId;
102 + final long baseValue;
103 + TrafficTreatment treatment;
104 + TrafficSelector selector;
105 +
106 + public MockFlowEntry(DeviceId deviceId, long id) {
107 + this.deviceId = deviceId;
108 + this.baseValue = id * 100;
109 + }
110 +
111 + @Override
112 + public FlowEntryState state() {
113 + return FlowEntryState.ADDED;
114 + }
115 +
116 + @Override
117 + public long life() {
118 + return baseValue + 11;
119 + }
120 +
121 + @Override
122 + public long packets() {
123 + return baseValue + 22;
124 + }
125 +
126 + @Override
127 + public long bytes() {
128 + return baseValue + 33;
129 + }
130 +
131 + @Override
132 + public long lastSeen() {
133 + return baseValue + 44;
134 + }
135 +
136 + @Override
137 + public int errType() {
138 + return 0;
139 + }
140 +
141 + @Override
142 + public int errCode() {
143 + return 0;
144 + }
145 +
146 + @Override
147 + public FlowId id() {
148 + final long id = baseValue + 55;
149 + return FlowId.valueOf(id);
150 + }
151 +
152 + @Override
153 + public short appId() {
154 + return 2;
155 + }
156 +
157 + @Override
158 + public GroupId groupId() {
159 + return new DefaultGroupId(3);
160 + }
161 +
162 + @Override
163 + public int priority() {
164 + return (int) (baseValue + 66);
165 + }
166 +
167 + @Override
168 + public DeviceId deviceId() {
169 + return deviceId;
170 + }
171 +
172 + @Override
173 + public TrafficSelector selector() {
174 + return selector;
175 + }
176 +
177 + @Override
178 + public TrafficTreatment treatment() {
179 + return treatment;
180 + }
181 +
182 + @Override
183 + public int timeout() {
184 + return (int) (baseValue + 77);
185 + }
186 +
187 + @Override
188 + public boolean isPermanent() {
189 + return false;
190 + }
191 + }
192 +
193 + public FlowsResourceTest() {
194 + super("org.onosproject.rest");
195 + }
196 +
197 + /**
198 + * Populates some flows used as testing data.
199 + */
200 + private void setupMockFlows() {
201 + flow2.treatment = DefaultTrafficTreatment.builder()
202 + .add(new L0ModificationInstruction.ModLambdaInstruction(
203 + L0ModificationInstruction.L0SubType.LAMBDA, (short) 4))
204 + .add(new L0ModificationInstruction.ModLambdaInstruction(
205 + L0ModificationInstruction.L0SubType.LAMBDA, (short) 5))
206 + .setEthDst(MacAddress.BROADCAST)
207 + .build();
208 + flow2.selector = DefaultTrafficSelector.builder()
209 + .matchEthType((short) 3)
210 + .matchIPProtocol((byte) 9)
211 + .build();
212 + flow4.treatment = DefaultTrafficTreatment.builder()
213 + .add(new L0ModificationInstruction.ModLambdaInstruction(
214 + L0ModificationInstruction.L0SubType.LAMBDA, (short) 6))
215 + .build();
216 + final Set<FlowEntry> flows1 = new HashSet<>();
217 + flows1.add(flow1);
218 + flows1.add(flow2);
219 +
220 + final Set<FlowEntry> flows2 = new HashSet<>();
221 + flows1.add(flow3);
222 + flows1.add(flow4);
223 +
224 + rules.put(deviceId1, flows1);
225 + rules.put(deviceId2, flows2);
226 +
227 + expect(mockFlowService.getFlowEntries(deviceId1))
228 + .andReturn(rules.get(deviceId1)).anyTimes();
229 + expect(mockFlowService.getFlowEntries(deviceId2))
230 + .andReturn(rules.get(deviceId2)).anyTimes();
231 + }
232 +
233 + /**
234 + * Sets up the global values for all the tests.
235 + */
236 + @Before
237 + public void setUp() {
238 + // Mock device service
239 + expect(mockDeviceService.getDevice(deviceId1))
240 + .andReturn(device1);
241 + expect(mockDeviceService.getDevice(deviceId2))
242 + .andReturn(device2);
243 + expect(mockDeviceService.getDevices())
244 + .andReturn(ImmutableSet.of(device1, device2));
245 +
246 + // Register the services needed for the test
247 + final CodecManager codecService = new CodecManager();
248 + codecService.activate();
249 + ServiceDirectory testDirectory =
250 + new TestServiceDirectory()
251 + .add(FlowRuleService.class, mockFlowService)
252 + .add(DeviceService.class, mockDeviceService)
253 + .add(CodecService.class, codecService);
254 +
255 + BaseResource.setServiceDirectory(testDirectory);
256 + }
257 +
258 + /**
259 + * Cleans up and verifies the mocks.
260 + *
261 + * @throws Exception if the super teardown fails.
262 + */
263 + @After
264 + public void tearDown() throws Exception {
265 + super.tearDown();
266 + verify(mockFlowService);
267 + }
268 +
269 + /**
270 + * Hamcrest matcher to check that a flow representation in JSON matches
271 + * the actual flow entry.
272 + */
273 + public static class FlowJsonMatcher extends TypeSafeMatcher<JsonObject> {
274 + private final FlowEntry flow;
275 + private String reason = "";
276 +
277 + public FlowJsonMatcher(FlowEntry flowValue) {
278 + flow = flowValue;
279 + }
280 +
281 + @Override
282 + public boolean matchesSafely(JsonObject jsonFlow) {
283 + // check id
284 + final String jsonId = jsonFlow.get("id").asString();
285 + final String flowId = Long.toString(flow.id().value());
286 + if (!jsonId.equals(flowId)) {
287 + reason = "id " + flow.id().toString();
288 + return false;
289 + }
290 +
291 + // check application id
292 + final int jsonAppId = jsonFlow.get("appId").asInt();
293 + if (jsonAppId != flow.appId()) {
294 + reason = "appId " + Short.toString(flow.appId());
295 + return false;
296 + }
297 +
298 + // check device id
299 + final String jsonDeviceId = jsonFlow.get("deviceId").asString();
300 + if (!jsonDeviceId.equals(flow.deviceId().toString())) {
301 + reason = "deviceId " + flow.deviceId();
302 + return false;
303 + }
304 +
305 + // check treatment and instructions array
306 + if (flow.treatment() != null) {
307 + final JsonObject jsonTreatment = jsonFlow.get("treatment").asObject();
308 + final JsonArray jsonInstructions = jsonTreatment.get("instructions").asArray();
309 + if (flow.treatment().instructions().size() != jsonInstructions.size()) {
310 + reason = "instructions array size of " +
311 + Integer.toString(flow.treatment().instructions().size());
312 + return false;
313 + }
314 + for (final Instruction instruction : flow.treatment().instructions()) {
315 + boolean instructionFound = false;
316 + final String instructionString = instruction.toString();
317 + for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) {
318 + final JsonValue value = jsonInstructions.get(instructionIndex);
319 + if (value.asString().equals(instructionString)) {
320 + instructionFound = true;
321 + }
322 + }
323 + if (!instructionFound) {
324 + reason = "instruction " + instructionString;
325 + return false;
326 + }
327 + }
328 + }
329 +
330 + // check selector and criteria array
331 + if (flow.selector() != null) {
332 + final JsonObject jsonTreatment = jsonFlow.get("selector").asObject();
333 + final JsonArray jsonCriteria = jsonTreatment.get("criteria").asArray();
334 + if (flow.selector().criteria().size() != jsonCriteria.size()) {
335 + reason = "criteria array size of " +
336 + Integer.toString(flow.selector().criteria().size());
337 + return false;
338 + }
339 + for (final Criterion criterion : flow.selector().criteria()) {
340 + boolean criterionFound = false;
341 + final String criterionString = criterion.toString();
342 + for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) {
343 + final JsonValue value = jsonCriteria.get(criterionIndex);
344 + if (value.asString().equals(criterionString)) {
345 + criterionFound = true;
346 + }
347 + }
348 + if (!criterionFound) {
349 + reason = "criterion " + criterionString;
350 + return false;
351 + }
352 + }
353 + }
354 +
355 + return true;
356 + }
357 +
358 + @Override
359 + public void describeTo(Description description) {
360 + description.appendText(reason);
361 + }
362 + }
363 +
364 + /**
365 + * Factory to allocate a flow matcher.
366 + *
367 + * @param flow flow object we are looking for
368 + * @return matcher
369 + */
370 + private static FlowJsonMatcher matchesFlow(FlowEntry flow) {
371 + return new FlowJsonMatcher(flow);
372 + }
373 +
374 + /**
375 + * Hamcrest matcher to check that a flow is represented properly in a JSON
376 + * array of flows.
377 + */
378 + public static class FlowJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
379 + private final FlowEntry flow;
380 + private String reason = "";
381 +
382 + public FlowJsonArrayMatcher(FlowEntry flowValue) {
383 + flow = flowValue;
384 + }
385 +
386 + @Override
387 + public boolean matchesSafely(JsonArray json) {
388 + boolean flowFound = false;
389 +
390 + for (int jsonFlowIndex = 0; jsonFlowIndex < json.size();
391 + jsonFlowIndex++) {
392 +
393 + final JsonObject jsonFlow = json.get(jsonFlowIndex).asObject();
394 +
395 + final String flowId = Long.toString(flow.id().value());
396 + final String jsonFlowId = jsonFlow.get("id").asString();
397 + if (jsonFlowId.equals(flowId)) {
398 + flowFound = true;
399 +
400 + // We found the correct flow, check attribute values
401 + assertThat(jsonFlow, matchesFlow(flow));
402 + }
403 + }
404 + if (!flowFound) {
405 + reason = "Flow with id " + flow.id().toString() + " not found";
406 + return false;
407 + } else {
408 + return true;
409 + }
410 + }
411 +
412 + @Override
413 + public void describeTo(Description description) {
414 + description.appendText(reason);
415 + }
416 + }
417 +
418 + /**
419 + * Factory to allocate a flow array matcher.
420 + *
421 + * @param flow flow object we are looking for
422 + * @return matcher
423 + */
424 + private static FlowJsonArrayMatcher hasFlow(FlowEntry flow) {
425 + return new FlowJsonArrayMatcher(flow);
426 + }
427 +
428 + /**
429 + * Tests the result of the rest api GET when there are no flows.
430 + */
431 + @Test
432 + public void testFlowsEmptyArray() {
433 + expect(mockFlowService.getFlowEntries(deviceId1))
434 + .andReturn(null).anyTimes();
435 + expect(mockFlowService.getFlowEntries(deviceId2))
436 + .andReturn(null).anyTimes();
437 + replay(mockFlowService);
438 + replay(mockDeviceService);
439 + final WebResource rs = resource();
440 + final String response = rs.path("flows").get(String.class);
441 + assertThat(response, is("{\"flows\":[]}"));
442 + }
443 +
444 + /**
445 + * Tests the result of the rest api GET when there are active flows.
446 + */
447 + @Test
448 + public void testFlowsPopulatedArray() {
449 + setupMockFlows();
450 + replay(mockFlowService);
451 + replay(mockDeviceService);
452 + final WebResource rs = resource();
453 + final String response = rs.path("flows").get(String.class);
454 + final JsonObject result = JsonObject.readFrom(response);
455 + assertThat(result, notNullValue());
456 +
457 + assertThat(result.names(), hasSize(1));
458 + assertThat(result.names().get(0), is("flows"));
459 + final JsonArray jsonFlows = result.get("flows").asArray();
460 + assertThat(jsonFlows, notNullValue());
461 + assertThat(jsonFlows, hasFlow(flow1));
462 + assertThat(jsonFlows, hasFlow(flow2));
463 + assertThat(jsonFlows, hasFlow(flow3));
464 + assertThat(jsonFlows, hasFlow(flow4));
465 + }
466 +
467 + /**
468 + * Tests the result of a rest api GET for a device.
469 + */
470 + @Test
471 + public void testFlowsSingleDevice() {
472 + setupMockFlows();
473 + final Set<FlowEntry> flows = new HashSet<>();
474 + flows.add(flow5);
475 + flows.add(flow6);
476 + expect(mockFlowService.getFlowEntries(anyObject()))
477 + .andReturn(flows).anyTimes();
478 + replay(mockFlowService);
479 + replay(mockDeviceService);
480 + final WebResource rs = resource();
481 + final String response = rs.path("flows/" + deviceId3).get(String.class);
482 + final JsonObject result = JsonObject.readFrom(response);
483 + assertThat(result, notNullValue());
484 +
485 + assertThat(result.names(), hasSize(1));
486 + assertThat(result.names().get(0), is("flows"));
487 + final JsonArray jsonFlows = result.get("flows").asArray();
488 + assertThat(jsonFlows, notNullValue());
489 + assertThat(jsonFlows, hasFlow(flow5));
490 + assertThat(jsonFlows, hasFlow(flow6));
491 + }
492 +
493 + /**
494 + * Tests the result of a rest api GET for a device.
495 + */
496 + @Test
497 + public void testFlowsSingleDeviceWithFlowId() {
498 + setupMockFlows();
499 + final Set<FlowEntry> flows = new HashSet<>();
500 + flows.add(flow5);
501 + flows.add(flow6);
502 + expect(mockFlowService.getFlowEntries(anyObject()))
503 + .andReturn(flows).anyTimes();
504 + replay(mockFlowService);
505 + replay(mockDeviceService);
506 + final WebResource rs = resource();
507 + final String response = rs.path("flows/" + deviceId3 + "/"
508 + + Long.toString(flow5.id().value())).get(String.class);
509 + final JsonObject result = JsonObject.readFrom(response);
510 + assertThat(result, notNullValue());
511 +
512 + assertThat(result.names(), hasSize(1));
513 + assertThat(result.names().get(0), is("flows"));
514 + final JsonArray jsonFlows = result.get("flows").asArray();
515 + assertThat(jsonFlows, notNullValue());
516 + assertThat(jsonFlows, hasFlow(flow5));
517 + assertThat(jsonFlows, not(hasFlow(flow6)));
518 + }
519 +
520 + /**
521 + * Tests that a fetch of a non-existent device object throws an exception.
522 + */
523 + @Test
524 + public void testBadGet() {
525 + expect(mockFlowService.getFlowEntries(deviceId1))
526 + .andReturn(null).anyTimes();
527 + expect(mockFlowService.getFlowEntries(deviceId2))
528 + .andReturn(null).anyTimes();
529 + replay(mockFlowService);
530 + replay(mockDeviceService);
531 +
532 + WebResource rs = resource();
533 + try {
534 + rs.path("flows/0").get(String.class);
535 + fail("Fetch of non-existent device did not throw an exception");
536 + } catch (UniformInterfaceException ex) {
537 + assertThat(ex.getMessage(),
538 + containsString("returned a response status of"));
539 + }
540 + }
541 +}
...@@ -256,6 +256,7 @@ public class IntentsResourceTest extends JerseyTest { ...@@ -256,6 +256,7 @@ public class IntentsResourceTest extends JerseyTest {
256 private static IntentJsonArrayMatcher hasIntent(Intent intent) { 256 private static IntentJsonArrayMatcher hasIntent(Intent intent) {
257 return new IntentJsonArrayMatcher(intent); 257 return new IntentJsonArrayMatcher(intent);
258 } 258 }
259 +
259 @Before 260 @Before
260 public void setUp() { 261 public void setUp() {
261 expect(mockIntentService.getIntents()).andReturn(intents).anyTimes(); 262 expect(mockIntentService.getIntents()).andReturn(intents).anyTimes();
......
...@@ -63,10 +63,7 @@ ...@@ -63,10 +63,7 @@
63 <scope>test</scope> 63 <scope>test</scope>
64 </dependency> 64 </dependency>
65 65
66 - <dependency> 66 +
67 - <groupId>com.google.guava</groupId>
68 - <artifactId>guava</artifactId>
69 - </dependency>
70 67
71 <dependency> 68 <dependency>
72 <groupId>com.sun.jersey</groupId> 69 <groupId>com.sun.jersey</groupId>
...@@ -131,6 +128,7 @@ ...@@ -131,6 +128,7 @@
131 com.fasterxml.jackson.databind, 128 com.fasterxml.jackson.databind,
132 com.fasterxml.jackson.databind.node, 129 com.fasterxml.jackson.databind.node,
133 com.google.common.base.*, 130 com.google.common.base.*,
131 + com.google.common.collect.*,
134 org.eclipse.jetty.websocket.*, 132 org.eclipse.jetty.websocket.*,
135 org.onlab.util.*, 133 org.onlab.util.*,
136 org.onlab.osgi.*, 134 org.onlab.osgi.*,
......