Ray Milkey
Committed by Gerrit Code Review

Implement REST APIs for link flow statistics

Change-Id: I65ca3cec9dd1771a70811afd319619827f7b9010
......@@ -47,6 +47,7 @@ import org.onosproject.net.intent.Constraint;
import org.onosproject.net.intent.HostToHostIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.PointToPointIntent;
import org.onosproject.net.statistic.Load;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyCluster;
import org.slf4j.Logger;
......@@ -97,6 +98,7 @@ public class CodecManager implements CodecService {
registerCodec(Group.class, new GroupCodec());
registerCodec(Driver.class, new DriverCodec());
registerCodec(GroupBucket.class, new GroupBucketCodec());
registerCodec(Load.class, new LoadCodec());
log.info("Started");
}
......
/*
* Copyright 2015 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.codec.impl;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.statistic.Load;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Codec for the Load class.
*/
public class LoadCodec extends JsonCodec<Load> {
private static final String RATE = "rate";
private static final String LATEST = "latest";
private static final String VALID = "valid";
private static final String TIME = "time";
@Override
public ObjectNode encode(Load load, CodecContext context) {
checkNotNull(load, "Load cannot be null");
return context.mapper().createObjectNode()
.put(RATE, load.rate())
.put(LATEST, load.latest())
.put(VALID, load.isValid())
.put(TIME, load.time());
}
}
/*
* Copyright 2015 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.codec.impl;
import org.junit.Test;
import org.onosproject.net.statistic.DefaultLoad;
import org.onosproject.net.statistic.Load;
import com.fasterxml.jackson.databind.JsonNode;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
/**
* Unit tests for Load codec.
*/
public class LoadCodecTest {
/**
* Tests encoding of a Load object.
*/
@Test
public void testLoadEncode() {
final long startTime = System.currentTimeMillis();
final Load load = new DefaultLoad(20, 10, 1);
final JsonNode node = new LoadCodec()
.encode(load, new MockCodecContext());
assertThat(node.get("valid").asBoolean(), is(true));
assertThat(node.get("latest").asLong(), is(20L));
assertThat(node.get("rate").asLong(), is(10L));
assertThat(node.get("time").asLong(), greaterThanOrEqualTo(startTime));
}
}
/*
* Copyright 2015 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 java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Link;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.statistic.Load;
import org.onosproject.net.statistic.StatisticService;
import org.onosproject.rest.AbstractWebResource;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.PortNumber.portNumber;
/**
* Statistics REST APIs.
*/
@Path("statistics")
public class StatisticsWebResource extends AbstractWebResource {
@Context
UriInfo uriInfo;
/**
* Gets the Load statistics for all links, or for a specific link.
*
* @param deviceId (optional) device ID for a specific link
* @param port (optional) port number for a specified link
* @return JSON encoded array lof Load objects
*/
@GET
@Path("flows/link")
@Produces(MediaType.APPLICATION_JSON)
public Response getLoads(@QueryParam("device") String deviceId,
@QueryParam("port") String port) {
Iterable<Link> links;
if (deviceId == null || port == null) {
links = get(LinkService.class).getLinks();
} else {
ConnectPoint connectPoint = new ConnectPoint(deviceId(deviceId),
portNumber(port));
links = get(LinkService.class).getLinks(connectPoint);
}
ObjectNode result = mapper().createObjectNode();
ArrayNode loads = mapper().createArrayNode();
JsonCodec<Load> loadCodec = codec(Load.class);
StatisticService statsService = getService(StatisticService.class);
StreamSupport.stream(Spliterators.spliteratorUnknownSize(
links.iterator(), Spliterator.ORDERED), false)
.forEach(link -> {
ObjectNode loadNode = loadCodec.encode(statsService.load(link), this);
UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
.path("links")
.queryParam("device", link.src().deviceId().toString())
.queryParam("port", link.src().port().toString());
loadNode.put("link", locationBuilder.build().toString());
loads.add(loadNode);
});
result.set("loads", loads);
return ok(result).build();
}
}
......@@ -76,7 +76,8 @@
org.onosproject.rest.resources.FlowsWebResource,
org.onosproject.rest.resources.TopologyWebResource,
org.onosproject.rest.resources.ConfigWebResource,
org.onosproject.rest.resources.PathsWebResource
org.onosproject.rest.resources.PathsWebResource,
org.onosproject.rest.resources.StatisticsWebResource
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
......
/*
* Copyright 2015 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.stream.IntStream;
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.codec.CodecService;
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.net.Link;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.statistic.DefaultLoad;
import org.onosproject.net.statistic.StatisticService;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.sun.jersey.api.client.WebResource;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.onosproject.net.NetTestTools.connectPoint;
import static org.onosproject.net.NetTestTools.link;
/**
* Unit tests for statistics REST APIs.
*/
public class StatisticsResourceTest extends ResourceTest {
Link link1 = link("src1", 1, "dst1", 1);
Link link2 = link("src2", 2, "dst2", 2);
Link link3 = link("src3", 3, "dst3", 3);
LinkService mockLinkService;
StatisticService mockStatisticService;
/**
* Initializes test mocks and environment.
*/
@Before
public void setUpTest() {
mockLinkService = createMock(LinkService.class);
expect(mockLinkService.getLinks())
.andReturn(ImmutableList.of(link1, link2, link3));
expect(mockLinkService.getLinks(connectPoint("0000000000000001", 2)))
.andReturn(ImmutableSet.of(link3));
mockStatisticService = createMock(StatisticService.class);
expect(mockStatisticService.load(link1))
.andReturn(new DefaultLoad(2, 1, 1));
expect(mockStatisticService.load(link2))
.andReturn(new DefaultLoad(22, 11, 1));
expect(mockStatisticService.load(link3))
.andReturn(new DefaultLoad(222, 111, 1));
replay(mockLinkService, mockStatisticService);
// Register the services needed for the test
CodecManager codecService = new CodecManager();
codecService.activate();
ServiceDirectory testDirectory =
new TestServiceDirectory()
.add(LinkService.class, mockLinkService)
.add(StatisticService.class, mockStatisticService)
.add(CodecService.class, codecService);
BaseResource.setServiceDirectory(testDirectory);
}
/**
* Checks that the values in a JSON representation of a Load are
* correct.
*
* @param load JSON for the Loan object
* @param rate expected vale fo rate
* @param latest expected value for latest
* @param valid expected value for valid flag
* @param device expected device ID
*/
private void checkValues(JsonObject load, int rate, int latest,
boolean valid, String device) {
assertThat(load, notNullValue());
assertThat(load.get("rate").asInt(), is(rate));
assertThat(load.get("latest").asInt(), is(latest));
assertThat(load.get("valid").asBoolean(), is(valid));
assertThat(load.get("time").asLong(),
lessThanOrEqualTo((System.currentTimeMillis())));
assertThat(load.get("link").asString(),
containsString("device=of:" + device));
}
/**
* Tests GET of a single Load statistics object.
*/
@Test
public void testSingleLoadGet() {
final WebResource rs = resource();
final String response = rs.path("statistics/flows/link")
.queryParam("device", "of:0000000000000001")
.queryParam("port", "2")
.get(String.class);
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("loads"));
final JsonArray jsonLoads = result.get("loads").asArray();
assertThat(jsonLoads, notNullValue());
assertThat(jsonLoads.size(), is(1));
JsonObject load1 = jsonLoads.get(0).asObject();
checkValues(load1, 111, 222, true, "src3");
}
/**
* Tests GET of all Load statistics objects.
*/
@Test
public void testLoadsGet() {
final WebResource rs = resource();
final String response = rs.path("statistics/flows/link/").get(String.class);
final JsonObject result = JsonObject.readFrom(response);
assertThat(result, notNullValue());
assertThat(result.names(), hasSize(1));
assertThat(result.names().get(0), is("loads"));
final JsonArray jsonLoads = result.get("loads").asArray();
assertThat(jsonLoads, notNullValue());
assertThat(jsonLoads.size(), is(3));
// Hash the loads by the current field to allow easy lookup if the
// order changes.
HashMap<Integer, JsonObject> currentMap = new HashMap<>();
IntStream.range(0, jsonLoads.size())
.forEach(index -> currentMap.put(
jsonLoads.get(index).asObject().get("latest").asInt(),
jsonLoads.get(index).asObject()));
JsonObject load1 = currentMap.get(2);
checkValues(load1, 1, 2, true, "src1");
JsonObject load2 = currentMap.get(22);
checkValues(load2, 11, 22, true, "src2");
JsonObject load3 = currentMap.get(222);
checkValues(load3, 111, 222, true, "src3");
}
}