Jian Li
Committed by Gerrit Code Review

[ONOS-3663] Implement control metrics REST API

Change-Id: Ifc901863e55cdd161d704ecd584242786671af87
/*
* Copyright 2016 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.cpman.codec;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.cpman.ControlLoad;
/**
* Control load codec.
*/
public final class ControlLoadCodec extends JsonCodec<ControlLoad> {
private static final String TIME = "time";
private static final String LATEST = "latest";
private static final String AVERAGE = "average";
@Override
public ObjectNode encode(ControlLoad controlLoad, CodecContext context) {
return context.mapper().createObjectNode()
.put(TIME, controlLoad.time())
.put(LATEST, controlLoad.latest())
.put(AVERAGE, controlLoad.average());
}
}
/*
* Copyright 2016 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.
*/
/**
* Implementations of the codec broker and cpman entity JSON codecs.
*/
package org.onosproject.cpman.codec;
/*
* Copyright 2016 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.cpman.rest;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onosproject.codec.CodecService;
import org.onosproject.cpman.ControlLoad;
import org.onosproject.cpman.codec.ControlLoadCodec;
import org.slf4j.Logger;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Implementation of the JSON codec brokering service for CPMan app.
*/
@Component(immediate = true)
public class CPManCodecRegistrator {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CodecService codecService;
@Activate
public void activate() {
codecService.registerCodec(ControlLoad.class, new ControlLoadCodec());
log.info("Started");
}
@Deactivate
public void deactivate() {
log.info("Stopped");
}
}
......@@ -26,6 +26,7 @@ import java.util.Set;
public class CPManWebApplication extends AbstractWebApplication {
@Override
public Set<Class<?>> getClasses() {
return getClasses(ControlMetricsCollectorWebResource.class);
return getClasses(SystemMetricsCollectorWebResource.class,
ControlMetricsWebResource.class);
}
}
......
/*
* Copyright 2016 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.cpman.rest;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.NodeId;
import org.onosproject.cpman.ControlLoad;
import org.onosproject.cpman.ControlMetricType;
import org.onosproject.cpman.ControlPlaneMonitorService;
import org.onosproject.net.DeviceId;
import org.onosproject.rest.AbstractWebResource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Optional;
import java.util.Set;
import static org.onosproject.cpman.ControlResource.CONTROL_MESSAGE_METRICS;
import static org.onosproject.cpman.ControlResource.CPU_METRICS;
import static org.onosproject.cpman.ControlResource.DISK_METRICS;
import static org.onosproject.cpman.ControlResource.MEMORY_METRICS;
import static org.onosproject.cpman.ControlResource.NETWORK_METRICS;
import static org.onosproject.cpman.ControlResource.Type.CONTROL_MESSAGE;
import static org.onosproject.cpman.ControlResource.Type.DISK;
import static org.onosproject.cpman.ControlResource.Type.NETWORK;
/**
* Query control metrics.
*/
@Path("metrics")
public class ControlMetricsWebResource extends AbstractWebResource {
private final ControlPlaneMonitorService monitorService =
get(ControlPlaneMonitorService.class);
private final ClusterService clusterService = get(ClusterService.class);
private final NodeId localNodeId = clusterService.getLocalNode().id();
private final ObjectNode root = mapper().createObjectNode();
/**
* Returns control message metrics of all devices.
*
* @return array of all control message metrics
*/
@GET
@Path("messages")
@Produces(MediaType.APPLICATION_JSON)
public Response controlMessageMetrics() {
ArrayNode deviceNodes = root.putArray("devices");
monitorService.availableResources(CONTROL_MESSAGE).forEach(name -> {
ObjectNode deviceNode = mapper().createObjectNode();
ObjectNode valueNode = mapper().createObjectNode();
metricsStats(monitorService, localNodeId, CONTROL_MESSAGE_METRICS,
DeviceId.deviceId(name), valueNode);
deviceNode.put("name", name);
deviceNode.set("value", valueNode);
deviceNodes.add(deviceNode);
});
return ok(root).build();
}
/**
* Returns control message metrics of a given device.
*
* @param deviceId device identification
* @return control message metrics of a given device
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("messages/{deviceId}")
public Response controlMessageMetrics(@PathParam("deviceId") String deviceId) {
metricsStats(monitorService, localNodeId, CONTROL_MESSAGE_METRICS,
DeviceId.deviceId(deviceId), root);
return ok(root).build();
}
/**
* Returns cpu metrics.
*
* @return cpu metrics
*/
@GET
@Path("cpu_metrics")
@Produces(MediaType.APPLICATION_JSON)
public Response cpuMetrics() {
metricsStats(monitorService, localNodeId, CPU_METRICS, root);
return ok(root).build();
}
/**
* Returns memory metrics.
*
* @return memory metrics
*/
@GET
@Path("memory_metrics")
@Produces(MediaType.APPLICATION_JSON)
public Response memoryMetrics() {
metricsStats(monitorService, localNodeId, MEMORY_METRICS, root);
return ok(root).build();
}
/**
* Returns disk metrics of all resources.
*
* @return disk metrics of all resources
*/
@GET
@Path("disk_metrics")
@Produces(MediaType.APPLICATION_JSON)
public Response diskMetrics() {
ArrayNode diskNodes = root.putArray("disks");
monitorService.availableResources(DISK).forEach(name -> {
ObjectNode diskNode = mapper().createObjectNode();
ObjectNode valueNode = mapper().createObjectNode();
metricsStats(monitorService, localNodeId, DISK_METRICS, name, valueNode);
diskNode.put("name", name);
diskNode.set("value", valueNode);
diskNodes.add(diskNode);
});
return ok(root).build();
}
/**
* Returns network metrics of all resources.
*
* @return network metrics of all resources
*/
@GET
@Path("network_metrics")
@Produces(MediaType.APPLICATION_JSON)
public Response networkMetrics() {
ArrayNode networkNodes = root.putArray("networks");
monitorService.availableResources(NETWORK).forEach(name -> {
ObjectNode networkNode = mapper().createObjectNode();
ObjectNode valueNode = mapper().createObjectNode();
metricsStats(monitorService, localNodeId, NETWORK_METRICS, name, valueNode);
networkNode.put("name", name);
networkNode.set("value", valueNode);
networkNodes.add(networkNode);
});
return ok(root).build();
}
/**
* Returns a collection of control message stats.
*
* @param service control plane monitoring service
* @param nodeId node identification
* @param typeSet a set of control message types
* @param did device identification
* @param node object node
* @return a collection of control message stats
*/
private ArrayNode metricsStats(ControlPlaneMonitorService service,
NodeId nodeId, Set<ControlMetricType> typeSet,
DeviceId did, ObjectNode node) {
return metricsStats(service, nodeId, typeSet, null, did, node);
}
/**
* Returns a collection of system metric stats.
*
* @param service control plane monitoring service
* @param nodeId node identification
* @param typeSet a set of system metric types
* @param node object node
* @return a collection of system metric stats
*/
private ArrayNode metricsStats(ControlPlaneMonitorService service,
NodeId nodeId, Set<ControlMetricType> typeSet,
ObjectNode node) {
return metricsStats(service, nodeId, typeSet, null, null, node);
}
/**
* Returns a collection of system metric stats.
*
* @param service control plane monitoring service
* @param nodeId node identification
* @param typeSet a set of control message types
* @param resourceName device identification
* @param node object node
* @return a collection of system metric stats
*/
private ArrayNode metricsStats(ControlPlaneMonitorService service,
NodeId nodeId, Set<ControlMetricType> typeSet,
String resourceName, ObjectNode node) {
return metricsStats(service, nodeId, typeSet, resourceName, null, node);
}
/**
* Returns a collection of control loads of the given control metric types.
*
* @param service control plane monitoring service
* @param nodeId node identification
* @param typeSet a group of control metric types
* @param name resource name
* @param did device identification
* @return a collection of control loads
*/
private ArrayNode metricsStats(ControlPlaneMonitorService service,
NodeId nodeId, Set<ControlMetricType> typeSet,
String name, DeviceId did, ObjectNode node) {
ArrayNode metricsNode = node.putArray("metrics");
if (name == null && did == null) {
typeSet.forEach(type -> {
ObjectNode metricNode = mapper().createObjectNode();
ControlLoad load = service.getLoad(nodeId, type, Optional.ofNullable(null));
if (load != null) {
metricNode.set(type.toString().toLowerCase(), codec(ControlLoad.class)
.encode(service.getLoad(nodeId, type, Optional.ofNullable(null)), this));
metricsNode.add(metricNode);
}
});
} else if (name == null) {
typeSet.forEach(type -> {
ObjectNode metricNode = mapper().createObjectNode();
ControlLoad load = service.getLoad(nodeId, type, Optional.of(did));
if (load != null) {
metricNode.set(type.toString().toLowerCase(),
codec(ControlLoad.class).encode(load, this));
metricsNode.add(metricNode);
}
});
} else if (did == null) {
typeSet.forEach(type -> {
ObjectNode metricNode = mapper().createObjectNode();
ControlLoad load = service.getLoad(nodeId, type, name);
if (load != null) {
metricNode.set(type.toString().toLowerCase(),
codec(ControlLoad.class).encode(load, this));
metricsNode.add(metricNode);
}
});
}
return metricsNode;
}
}
......@@ -40,16 +40,16 @@ import java.util.Optional;
import static org.onlab.util.Tools.nullIsIllegal;
/**
* Collect control plane metrics.
* Collect system metrics.
*/
@Path("collector")
public class ControlMetricsCollectorWebResource extends AbstractWebResource {
public class SystemMetricsCollectorWebResource extends AbstractWebResource {
final ControlPlaneMonitorService service = get(ControlPlaneMonitorService.class);
public static final int UPDATE_INTERVAL_IN_MINUTE = 1;
public static final String INVALID_SYSTEM_SPECS = "Invalid system specifications";
public static final String INVALID_RESOURCE_NAME = "Invalid resource name";
public static final String INVALID_REQUEST = "Invalid request";
private final ControlPlaneMonitorService service = get(ControlPlaneMonitorService.class);
private static final int UPDATE_INTERVAL_IN_MINUTE = 1;
private static final String INVALID_SYSTEM_SPECS = "Invalid system specifications";
private static final String INVALID_RESOURCE_NAME = "Invalid resource name";
private static final String INVALID_REQUEST = "Invalid request";
/**
* Collects CPU metrics.
......
/*
* Copyright 2016 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.cpman.rest;
import com.google.common.collect.ImmutableSet;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Before;
import org.junit.Test;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.osgi.TestServiceDirectory;
import org.onlab.packet.IpAddress;
import org.onlab.rest.BaseResource;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.ControllerNode;
import org.onosproject.cluster.NodeId;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.cpman.ControlLoad;
import org.onosproject.cpman.ControlPlaneMonitorService;
import org.onosproject.cpman.codec.ControlLoadCodec;
import javax.ws.rs.client.WebTarget;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyString;
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.is;
import static org.junit.Assert.assertThat;
/**
* Unit test for ControlMetrics REST API.
*/
public class ControlMetricsResourceTest extends JerseyTest {
final ControlPlaneMonitorService mockControlPlaneMonitorService =
createMock(ControlPlaneMonitorService.class);
final ClusterService mockClusterService = createMock(ClusterService.class);
Set<String> resourceSet = ImmutableSet.of("resource1", "resource2");
NodeId nodeId;
ControlLoad mockControlLoad;
private static final String PREFIX = "metrics";
/**
* Constructs a control metrics resource test instance.
*/
public ControlMetricsResourceTest() {
super(ResourceConfig.forApplicationClass(CPManWebApplication.class));
}
/**
* Mock class for a controller node.
*/
private static class MockControllerNode implements ControllerNode {
final NodeId id;
public MockControllerNode(NodeId id) {
this.id = id;
}
@Override
public NodeId id() {
return this.id;
}
@Override
public IpAddress ip() {
return null;
}
@Override
public int tcpPort() {
return 0;
}
}
private static class MockControlLoad implements ControlLoad {
@Override
public long average(int duration, TimeUnit unit) {
return 0;
}
@Override
public long average() {
return 10L;
}
@Override
public long[] recent(int duration, TimeUnit unit) {
return new long[0];
}
@Override
public long[] all() {
return new long[0];
}
@Override
public long rate() {
return 0;
}
@Override
public long latest() {
return 10L;
}
@Override
public boolean isValid() {
return false;
}
@Override
public long time() {
return 20L;
}
}
/**
* Sets up the global values for all the tests.
*/
@Before
public void setUpTest() {
final CodecManager codecService = new CodecManager();
codecService.activate();
codecService.registerCodec(ControlLoad.class, new ControlLoadCodec());
ServiceDirectory testDirectory =
new TestServiceDirectory()
.add(ControlPlaneMonitorService.class,
mockControlPlaneMonitorService)
.add(ClusterService.class, mockClusterService)
.add(CodecService.class, codecService);
BaseResource.setServiceDirectory(testDirectory);
nodeId = new NodeId("1");
mockControlLoad = new MockControlLoad();
ControllerNode mockControllerNode = new MockControllerNode(nodeId);
expect(mockClusterService.getLocalNode()).andReturn(mockControllerNode).anyTimes();
replay(mockClusterService);
}
/**
* Tests the results of the REST API GET when there are no active entries.
*/
@Test
public void testResourceEmptyArray() {
expect(mockControlPlaneMonitorService.availableResources(anyObject()))
.andReturn(ImmutableSet.of()).once();
replay(mockControlPlaneMonitorService);
final WebTarget wt = target();
final String response = wt.path(PREFIX + "/disk_metrics").request().get(String.class);
assertThat(response, is("{\"disks\":[]}"));
verify(mockControlPlaneMonitorService);
}
/**
* Tests the results of the rest api GET when there are active metrics.
*/
@Test
public void testResourcePopulatedArray() {
expect(mockControlPlaneMonitorService.availableResources(anyObject()))
.andReturn(resourceSet).once();
expect(mockControlPlaneMonitorService.getLoad(anyObject(), anyObject(),
anyString())).andReturn(null).times(4);
replay(mockControlPlaneMonitorService);
final WebTarget wt = target();
final String response = wt.path(PREFIX + "/disk_metrics").request().get(String.class);
assertThat(response, is("{\"disks\":[{\"name\":\"resource1\",\"value\":{\"metrics\":[]}}," +
"{\"name\":\"resource2\",\"value\":{\"metrics\":[]}}]}"));
}
}