/* * Copyright 2016-present 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.resources; import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.glassfish.jersey.client.ClientProperties; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; 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.cluster.NodeId; import org.onosproject.codec.CodecService; import org.onosproject.codec.impl.CodecManager; import org.onosproject.net.Annotations; import org.onosproject.net.DefaultAnnotations; import org.onosproject.net.DeviceId; import org.onosproject.net.region.Region; import org.onosproject.net.region.RegionAdminService; import org.onosproject.net.region.RegionId; import org.onosproject.net.region.RegionService; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.InputStream; import java.net.HttpURLConnection; import java.util.List; import java.util.Set; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; /** * Unit tests for region REST APIs. */ public class RegionsResourceTest extends ResourceTest { final RegionService mockRegionService = createMock(RegionService.class); final RegionAdminService mockRegionAdminService = createMock(RegionAdminService.class); final RegionId regionId1 = RegionId.regionId("1"); final RegionId regionId2 = RegionId.regionId("2"); final RegionId regionId3 = RegionId.regionId("3"); final MockRegion region1 = new MockRegion(regionId1, "r1", Region.Type.RACK); final MockRegion region2 = new MockRegion(regionId2, "r2", Region.Type.ROOM); final MockRegion region3 = new MockRegion(regionId3, "r3", Region.Type.CAMPUS); /** * Mock class for a region. */ private static class MockRegion implements Region { private final RegionId id; private final String name; private final Type type; private final List<Set<NodeId>> masters; public MockRegion(RegionId id, String name, Type type) { this.id = id; this.name = name; this.type = type; final NodeId nodeId1 = NodeId.nodeId("1"); final NodeId nodeId2 = NodeId.nodeId("2"); final NodeId nodeId3 = NodeId.nodeId("3"); final NodeId nodeId4 = NodeId.nodeId("4"); Set<NodeId> nodeIds1 = ImmutableSet.of(nodeId1); Set<NodeId> nodeIds2 = ImmutableSet.of(nodeId1, nodeId2); Set<NodeId> nodeIds3 = ImmutableSet.of(nodeId1, nodeId2, nodeId3); Set<NodeId> nodeIds4 = ImmutableSet.of(nodeId1, nodeId2, nodeId3, nodeId4); this.masters = ImmutableList.of(nodeIds1, nodeIds2, nodeIds3, nodeIds4); } @Override public RegionId id() { return this.id; } @Override public String name() { return this.name; } @Override public Type type() { return this.type; } @Override public List<Set<NodeId>> masters() { return this.masters; } @Override public Annotations annotations() { return DefaultAnnotations.EMPTY; } } /** * Sets up the global values for all the tests. */ @Before public void setupTest() { final CodecManager codecService = new CodecManager(); codecService.activate(); ServiceDirectory testDirectory = new TestServiceDirectory() .add(RegionService.class, mockRegionService) .add(RegionAdminService.class, mockRegionAdminService) .add(CodecService.class, codecService); BaseResource.setServiceDirectory(testDirectory); } /** * Hamcrest matcher to check that a region representation in JSON matches * the actual region. */ public static class RegionJsonMatcher extends TypeSafeMatcher<JsonObject> { private final Region region; private String reason = ""; public RegionJsonMatcher(Region regionValue) { this.region = regionValue; } @Override protected boolean matchesSafely(JsonObject jsonRegion) { // check id String jsonRegionId = jsonRegion.get("id").asString(); String regionId = region.id().toString(); if (!jsonRegionId.equals(regionId)) { reason = "region id was " + jsonRegionId; return false; } // check type String jsonType = jsonRegion.get("type").asString(); String type = region.type().toString(); if (!jsonType.equals(type)) { reason = "type was " + jsonType; return false; } // check name String jsonName = jsonRegion.get("name").asString(); String name = region.name(); if (!jsonName.equals(name)) { reason = "name was " + jsonName; return false; } // check size of master array JsonArray jsonMasters = jsonRegion.get("masters").asArray(); if (jsonMasters.size() != region.masters().size()) { reason = "masters size was " + jsonMasters.size(); return false; } // check master for (Set<NodeId> set : region.masters()) { boolean masterFound = false; for (int masterIndex = 0; masterIndex < jsonMasters.size(); masterIndex++) { masterFound = checkEquality(jsonMasters.get(masterIndex).asArray(), set); } if (!masterFound) { reason = "master not found " + set.toString(); return false; } } return true; } @Override public void describeTo(Description description) { description.appendText(reason); } private Set<NodeId> jsonToSet(JsonArray nodes) { final Set<NodeId> nodeIds = Sets.newHashSet(); nodes.forEach(node -> nodeIds.add(NodeId.nodeId(node.asString()))); return nodeIds; } private boolean checkEquality(JsonArray nodes, Set<NodeId> nodeIds) { Set<NodeId> jsonSet = jsonToSet(nodes); if (jsonSet.size() == nodes.size()) { return jsonSet.containsAll(nodeIds); } return false; } } private static RegionJsonMatcher matchesRegion(Region region) { return new RegionJsonMatcher(region); } /** * Hamcrest matcher to check that a region is represented properly in a JSON * array of regions. */ public static class RegionJsonArrayMatcher extends TypeSafeMatcher<JsonArray> { private final Region region; private String reason = ""; public RegionJsonArrayMatcher(Region regionValue) { this.region = regionValue; } @Override protected boolean matchesSafely(JsonArray json) { boolean regionFound = false; for (int jsonRegionIndex = 0; jsonRegionIndex < json.size(); jsonRegionIndex++) { final JsonObject jsonRegion = json.get(jsonRegionIndex).asObject(); final String regionId = region.id().toString(); final String jsonRegionId = jsonRegion.get("id").asString(); if (jsonRegionId.equals(regionId)) { regionFound = true; assertThat(jsonRegion, matchesRegion(region)); } } if (!regionFound) { reason = "Region with id " + region.id().toString() + " not found"; return false; } else { return true; } } @Override public void describeTo(Description description) { description.appendText(reason); } } /** * Factory to allocate a region array matcher. * * @param region region object we are looking for * @return matcher */ private static RegionJsonArrayMatcher hasRegion(Region region) { return new RegionJsonArrayMatcher(region); } @Test public void testRegionEmptyArray() { expect(mockRegionService.getRegions()).andReturn(ImmutableSet.of()).anyTimes(); replay((mockRegionService)); final WebTarget wt = target(); final String response = wt.path("regions").request().get(String.class); assertThat(response, is("{\"regions\":[]}")); verify(mockRegionService); } /** * Tests the results of the REST API GET when there are active regions. */ @Test public void testRegionsPopulatedArray() { final Set<Region> regions = ImmutableSet.of(region1, region2, region3); expect(mockRegionService.getRegions()).andReturn(regions).anyTimes(); replay(mockRegionService); final WebTarget wt = target(); final String response = wt.path("regions").request().get(String.class); final JsonObject result = Json.parse(response).asObject(); assertThat(result, notNullValue()); assertThat(result.names(), hasSize(1)); assertThat(result.names().get(0), is("regions")); final JsonArray jsonRegions = result.get("regions").asArray(); assertThat(jsonRegions, notNullValue()); assertThat(jsonRegions, hasRegion(region1)); assertThat(jsonRegions, hasRegion(region2)); assertThat(jsonRegions, hasRegion(region3)); verify(mockRegionService); } /** * Tests the result of a REST API GET for a region with region id. */ @Test public void testGetRegionById() { expect(mockRegionService.getRegion(anyObject())).andReturn(region1).anyTimes(); replay(mockRegionService); final WebTarget wt = target(); final String response = wt.path("regions/" + regionId1.toString()).request().get(String.class); final JsonObject result = Json.parse(response).asObject(); assertThat(result, notNullValue()); assertThat(result, matchesRegion(region1)); verify(mockRegionService); } /** * Tests creating a region with POST. */ @Test public void testRegionPost() { mockRegionAdminService.createRegion(anyObject(), anyObject(), anyObject(), anyObject()); expectLastCall().andReturn(region2).anyTimes(); replay(mockRegionAdminService); WebTarget wt = target(); InputStream jsonStream = RegionsResourceTest.class .getResourceAsStream("post-region.json"); Response response = wt.path("regions") .request(MediaType.APPLICATION_JSON_TYPE) .post(Entity.json(jsonStream)); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED)); verify(mockRegionAdminService); } /** * Tests updating a region with PUT. */ @Test public void testRegionPut() { mockRegionAdminService.updateRegion(anyObject(), anyObject(), anyObject(), anyObject()); expectLastCall().andReturn(region1).anyTimes(); replay(mockRegionAdminService); WebTarget wt = target(); InputStream jsonStream = RegionsResourceTest.class .getResourceAsStream("post-region.json"); Response response = wt.path("regions/" + region1.id().toString()) .request(MediaType.APPLICATION_JSON_TYPE) .put(Entity.json(jsonStream)); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK)); verify(mockRegionAdminService); } /** * Tests deleting a region with DELETE. */ @Test public void testRegionDelete() { mockRegionAdminService.removeRegion(anyObject()); expectLastCall(); replay(mockRegionAdminService); WebTarget wt = target(); Response response = wt.path("regions/" + region1.id().toString()) .request().delete(); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT)); verify(mockRegionAdminService); } /** * Tests retrieving device ids that are associated with the given region. */ @Test public void testGetRegionDevices() { final DeviceId deviceId1 = DeviceId.deviceId("1"); final DeviceId deviceId2 = DeviceId.deviceId("2"); final DeviceId deviceId3 = DeviceId.deviceId("3"); final Set<DeviceId> deviceIds = ImmutableSet.of(deviceId1, deviceId2, deviceId3); expect(mockRegionService.getRegionDevices(anyObject())) .andReturn(deviceIds).anyTimes(); replay(mockRegionService); final WebTarget wt = target(); final String response = wt.path("regions/" + region1.id().toString() + "/devices").request().get(String.class); final JsonObject result = Json.parse(response).asObject(); assertThat(result, notNullValue()); assertThat(result.names(), hasSize(1)); assertThat(result.names().get(0), is("deviceIds")); final JsonArray jsonDeviceIds = result.get("deviceIds").asArray(); assertThat(jsonDeviceIds.size(), is(3)); assertThat(jsonDeviceIds.get(0).asString(), is("1")); assertThat(jsonDeviceIds.get(1).asString(), is("2")); assertThat(jsonDeviceIds.get(2).asString(), is("3")); verify(mockRegionService); } /** * Tests creating a flow with POST. */ @Test public void testAddDevicesPostWithoutRegion() { expect(mockRegionService.getRegion(anyObject())).andReturn(null).anyTimes(); replay(mockRegionService); WebTarget wt = target(); InputStream jsonStream = RegionsResourceTest.class .getResourceAsStream("region-deviceIds.json"); Response response = wt.path("regions/" + region1.id() + "/devices") .request(MediaType.APPLICATION_JSON_TYPE) .post(Entity.json(jsonStream)); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NOT_FOUND)); verify(mockRegionService); } /** * Tests adding a set of devices in region with POST. */ @Test public void testAddDevicesPost() { mockRegionAdminService.addDevices(anyObject(), anyObject()); expectLastCall(); replay(mockRegionAdminService); expect(mockRegionService.getRegion(anyObject())).andReturn(region1).anyTimes(); replay(mockRegionService); WebTarget wt = target(); InputStream jsonStream = RegionsResourceTest.class .getResourceAsStream("region-deviceIds.json"); Response response = wt.path("regions/" + region1.id().toString() + "/devices") .request(MediaType.APPLICATION_JSON_TYPE) .post(Entity.json(jsonStream)); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED)); verify(mockRegionAdminService); } /** * Tests deleting a set of devices contained in the given region with DELETE. */ @Test public void testRemoveDevicesDelete() { mockRegionAdminService.removeDevices(anyObject(), anyObject()); expectLastCall(); replay(mockRegionAdminService); expect(mockRegionService.getRegion(anyObject())).andReturn(region1).anyTimes(); replay(mockRegionService); WebTarget wt = target() .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true); InputStream jsonStream = RegionsResourceTest.class .getResourceAsStream("region-deviceIds.json"); // FIXME: need to consider whether to use jsonStream for entry deletion Response response = wt.path("regions/" + region1.id().toString() + "/devices") .request().method("DELETE", Entity.json(jsonStream)); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT)); verify(mockRegionAdminService); } }