blob: 5d50eac8783ca0f55155fb47d120e682d0f251a6 [file] [log] [blame]
/*
* 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.HashMap;
import java.util.HashSet;
import java.util.Set;
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.packet.MacAddress;
import org.onlab.rest.BaseResource;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.core.GroupId;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowId;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.L0ModificationInstruction;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import com.google.common.collect.ImmutableSet;
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.anyObject;
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.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Unit tests for Flows REST APIs.
*/
public class FlowsResourceTest extends JerseyTest {
final FlowRuleService mockFlowService = createMock(FlowRuleService.class);
final HashMap<DeviceId, Set<FlowEntry>> rules = new HashMap<>();
final DeviceService mockDeviceService = createMock(DeviceService.class);
final DeviceId deviceId1 = DeviceId.deviceId("1");
final DeviceId deviceId2 = DeviceId.deviceId("2");
final DeviceId deviceId3 = DeviceId.deviceId("3");
final Device device1 = new DefaultDevice(null, deviceId1, Device.Type.OTHER,
"", "", "", "", null);
final Device device2 = new DefaultDevice(null, deviceId2, Device.Type.OTHER,
"", "", "", "", null);
final MockFlowEntry flow1 = new MockFlowEntry(deviceId1, 1);
final MockFlowEntry flow2 = new MockFlowEntry(deviceId1, 2);
final MockFlowEntry flow3 = new MockFlowEntry(deviceId2, 3);
final MockFlowEntry flow4 = new MockFlowEntry(deviceId2, 4);
final MockFlowEntry flow5 = new MockFlowEntry(deviceId2, 5);
final MockFlowEntry flow6 = new MockFlowEntry(deviceId2, 6);
/**
* Mock class for a flow entry.
*/
private static class MockFlowEntry implements FlowEntry {
final DeviceId deviceId;
final long baseValue;
TrafficTreatment treatment;
TrafficSelector selector;
public MockFlowEntry(DeviceId deviceId, long id) {
this.deviceId = deviceId;
this.baseValue = id * 100;
}
@Override
public FlowEntryState state() {
return FlowEntryState.ADDED;
}
@Override
public long life() {
return baseValue + 11;
}
@Override
public long packets() {
return baseValue + 22;
}
@Override
public long bytes() {
return baseValue + 33;
}
@Override
public long lastSeen() {
return baseValue + 44;
}
@Override
public int errType() {
return 0;
}
@Override
public int errCode() {
return 0;
}
@Override
public FlowId id() {
final long id = baseValue + 55;
return FlowId.valueOf(id);
}
@Override
public short appId() {
return 2;
}
@Override
public GroupId groupId() {
return new DefaultGroupId(3);
}
@Override
public int priority() {
return (int) (baseValue + 66);
}
@Override
public DeviceId deviceId() {
return deviceId;
}
@Override
public TrafficSelector selector() {
return selector;
}
@Override
public TrafficTreatment treatment() {
return treatment;
}
@Override
public int timeout() {
return (int) (baseValue + 77);
}
@Override
public boolean isPermanent() {
return false;
}
}
public FlowsResourceTest() {
super("org.onosproject.rest");
}
/**
* Populates some flows used as testing data.
*/
private void setupMockFlows() {
flow2.treatment = DefaultTrafficTreatment.builder()
.add(new L0ModificationInstruction.ModLambdaInstruction(
L0ModificationInstruction.L0SubType.LAMBDA, (short) 4))
.add(new L0ModificationInstruction.ModLambdaInstruction(
L0ModificationInstruction.L0SubType.LAMBDA, (short) 5))
.setEthDst(MacAddress.BROADCAST)
.build();
flow2.selector = DefaultTrafficSelector.builder()
.matchEthType((short) 3)
.matchIPProtocol((byte) 9)
.build();
flow4.treatment = DefaultTrafficTreatment.builder()
.add(new L0ModificationInstruction.ModLambdaInstruction(
L0ModificationInstruction.L0SubType.LAMBDA, (short) 6))
.build();
final Set<FlowEntry> flows1 = new HashSet<>();
flows1.add(flow1);
flows1.add(flow2);
final Set<FlowEntry> flows2 = new HashSet<>();
flows1.add(flow3);
flows1.add(flow4);
rules.put(deviceId1, flows1);
rules.put(deviceId2, flows2);
expect(mockFlowService.getFlowEntries(deviceId1))
.andReturn(rules.get(deviceId1)).anyTimes();
expect(mockFlowService.getFlowEntries(deviceId2))
.andReturn(rules.get(deviceId2)).anyTimes();
}
/**
* Sets up the global values for all the tests.
*/
@Before
public void setUp() {
// Mock device service
expect(mockDeviceService.getDevice(deviceId1))
.andReturn(device1);
expect(mockDeviceService.getDevice(deviceId2))
.andReturn(device2);
expect(mockDeviceService.getDevices())
.andReturn(ImmutableSet.of(device1, device2));
// Register the services needed for the test
final CodecManager codecService = new CodecManager();
codecService.activate();
ServiceDirectory testDirectory =
new TestServiceDirectory()
.add(FlowRuleService.class, mockFlowService)
.add(DeviceService.class, mockDeviceService)
.add(CodecService.class, codecService);
BaseResource.setServiceDirectory(testDirectory);
}
/**
* Cleans up and verifies the mocks.
*
* @throws Exception if the super teardown fails.
*/
@After
public void tearDown() throws Exception {
super.tearDown();
verify(mockFlowService);
}
/**
* Hamcrest matcher to check that a flow representation in JSON matches
* the actual flow entry.
*/
public static class FlowJsonMatcher extends TypeSafeMatcher<JsonObject> {
private final FlowEntry flow;
private String reason = "";
public FlowJsonMatcher(FlowEntry flowValue) {
flow = flowValue;
}
@Override
public boolean matchesSafely(JsonObject jsonFlow) {
// check id
final String jsonId = jsonFlow.get("id").asString();
final String flowId = Long.toString(flow.id().value());
if (!jsonId.equals(flowId)) {
reason = "id " + flow.id().toString();
return false;
}
// check application id
final int jsonAppId = jsonFlow.get("appId").asInt();
if (jsonAppId != flow.appId()) {
reason = "appId " + Short.toString(flow.appId());
return false;
}
// check device id
final String jsonDeviceId = jsonFlow.get("deviceId").asString();
if (!jsonDeviceId.equals(flow.deviceId().toString())) {
reason = "deviceId " + flow.deviceId();
return false;
}
// check treatment and instructions array
if (flow.treatment() != null) {
final JsonObject jsonTreatment = jsonFlow.get("treatment").asObject();
final JsonArray jsonInstructions = jsonTreatment.get("instructions").asArray();
if (flow.treatment().instructions().size() != jsonInstructions.size()) {
reason = "instructions array size of " +
Integer.toString(flow.treatment().instructions().size());
return false;
}
for (final Instruction instruction : flow.treatment().instructions()) {
boolean instructionFound = false;
final String instructionString = instruction.toString();
for (int instructionIndex = 0; instructionIndex < jsonInstructions.size(); instructionIndex++) {
final JsonValue value = jsonInstructions.get(instructionIndex);
if (value.asString().equals(instructionString)) {
instructionFound = true;
}
}
if (!instructionFound) {
reason = "instruction " + instructionString;
return false;
}
}
}
// check selector and criteria array
if (flow.selector() != null) {
final JsonObject jsonTreatment = jsonFlow.get("selector").asObject();
final JsonArray jsonCriteria = jsonTreatment.get("criteria").asArray();
if (flow.selector().criteria().size() != jsonCriteria.size()) {
reason = "criteria array size of " +
Integer.toString(flow.selector().criteria().size());
return false;
}
for (final Criterion criterion : flow.selector().criteria()) {
boolean criterionFound = false;
final String criterionString = criterion.toString();
for (int criterionIndex = 0; criterionIndex < jsonCriteria.size(); criterionIndex++) {
final JsonValue value = jsonCriteria.get(criterionIndex);
if (value.asString().equals(criterionString)) {
criterionFound = true;
}
}
if (!criterionFound) {
reason = "criterion " + criterionString;
return false;
}
}
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText(reason);
}
}
/**
* Factory to allocate a flow matcher.
*
* @param flow flow object we are looking for
* @return matcher
*/
private static FlowJsonMatcher matchesFlow(FlowEntry flow) {
return new FlowJsonMatcher(flow);
}
/**
* Hamcrest matcher to check that a flow is represented properly in a JSON
* array of flows.
*/
public static class FlowJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
private final FlowEntry flow;
private String reason = "";
public FlowJsonArrayMatcher(FlowEntry flowValue) {
flow = flowValue;
}
@Override
public boolean matchesSafely(JsonArray json) {
boolean flowFound = false;
for (int jsonFlowIndex = 0; jsonFlowIndex < json.size();
jsonFlowIndex++) {
final JsonObject jsonFlow = json.get(jsonFlowIndex).asObject();
final String flowId = Long.toString(flow.id().value());
final String jsonFlowId = jsonFlow.get("id").asString();
if (jsonFlowId.equals(flowId)) {
flowFound = true;
// We found the correct flow, check attribute values
assertThat(jsonFlow, matchesFlow(flow));
}
}
if (!flowFound) {
reason = "Flow with id " + flow.id().toString() + " not found";
return false;
} else {
return true;
}
}
@Override
public void describeTo(Description description) {
description.appendText(reason);
}
}
/**
* Factory to allocate a flow array matcher.
*
* @param flow flow object we are looking for
* @return matcher
*/
private static FlowJsonArrayMatcher hasFlow(FlowEntry flow) {
return new FlowJsonArrayMatcher(flow);
}
/**
* Tests the result of the rest api GET when there are no flows.
*/
@Test
public void testFlowsEmptyArray() {
expect(mockFlowService.getFlowEntries(deviceId1))
.andReturn(null).anyTimes();
expect(mockFlowService.getFlowEntries(deviceId2))
.andReturn(null).anyTimes();
replay(mockFlowService);
replay(mockDeviceService);
final WebResource rs = resource();
final String response = rs.path("flows").get(String.class);
assertThat(response, is("{\"flows\":[]}"));
}
/**
* Tests the result of the rest api GET when there are active flows.
*/
@Test
public void testFlowsPopulatedArray() {
setupMockFlows();
replay(mockFlowService);
replay(mockDeviceService);
final WebResource rs = resource();
final String response = rs.path("flows").get(String.class);
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("flows"));
final JsonArray jsonFlows = result.get("flows").asArray();
assertThat(jsonFlows, notNullValue());
assertThat(jsonFlows, hasFlow(flow1));
assertThat(jsonFlows, hasFlow(flow2));
assertThat(jsonFlows, hasFlow(flow3));
assertThat(jsonFlows, hasFlow(flow4));
}
/**
* Tests the result of a rest api GET for a device.
*/
@Test
public void testFlowsSingleDevice() {
setupMockFlows();
final Set<FlowEntry> flows = new HashSet<>();
flows.add(flow5);
flows.add(flow6);
expect(mockFlowService.getFlowEntries(anyObject()))
.andReturn(flows).anyTimes();
replay(mockFlowService);
replay(mockDeviceService);
final WebResource rs = resource();
final String response = rs.path("flows/" + deviceId3).get(String.class);
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("flows"));
final JsonArray jsonFlows = result.get("flows").asArray();
assertThat(jsonFlows, notNullValue());
assertThat(jsonFlows, hasFlow(flow5));
assertThat(jsonFlows, hasFlow(flow6));
}
/**
* Tests the result of a rest api GET for a device.
*/
@Test
public void testFlowsSingleDeviceWithFlowId() {
setupMockFlows();
final Set<FlowEntry> flows = new HashSet<>();
flows.add(flow5);
flows.add(flow6);
expect(mockFlowService.getFlowEntries(anyObject()))
.andReturn(flows).anyTimes();
replay(mockFlowService);
replay(mockDeviceService);
final WebResource rs = resource();
final String response = rs.path("flows/" + deviceId3 + "/"
+ Long.toString(flow5.id().value())).get(String.class);
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("flows"));
final JsonArray jsonFlows = result.get("flows").asArray();
assertThat(jsonFlows, notNullValue());
assertThat(jsonFlows, hasFlow(flow5));
assertThat(jsonFlows, not(hasFlow(flow6)));
}
/**
* Tests that a fetch of a non-existent device object throws an exception.
*/
@Test
public void testBadGet() {
expect(mockFlowService.getFlowEntries(deviceId1))
.andReturn(null).anyTimes();
expect(mockFlowService.getFlowEntries(deviceId2))
.andReturn(null).anyTimes();
replay(mockFlowService);
replay(mockDeviceService);
WebResource rs = resource();
try {
rs.path("flows/0").get(String.class);
fail("Fetch of non-existent device did not throw an exception");
} catch (UniformInterfaceException ex) {
assertThat(ex.getMessage(),
containsString("returned a response status of"));
}
}
}