[ONOS-3663] Implement control metrics REST API

Change-Id: Ifc901863e55cdd161d704ecd584242786671af87
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/codec/ControlLoadCodec.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/codec/ControlLoadCodec.java
new file mode 100644
index 0000000..9beb929
--- /dev/null
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/codec/ControlLoadCodec.java
@@ -0,0 +1,39 @@
+/*
+ * 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());
+    }
+}
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/codec/package-info.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/codec/package-info.java
new file mode 100644
index 0000000..645326a
--- /dev/null
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/codec/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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;
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManCodecRegistrator.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManCodecRegistrator.java
new file mode 100644
index 0000000..d710e7f
--- /dev/null
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManCodecRegistrator.java
@@ -0,0 +1,52 @@
+/*
+ * 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");
+    }
+}
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManWebApplication.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManWebApplication.java
index 26566f8..fc90e41 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManWebApplication.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/CPManWebApplication.java
@@ -26,6 +26,7 @@
 public class CPManWebApplication extends AbstractWebApplication {
     @Override
     public Set<Class<?>> getClasses() {
-        return getClasses(ControlMetricsCollectorWebResource.class);
+        return getClasses(SystemMetricsCollectorWebResource.class,
+                          ControlMetricsWebResource.class);
     }
 }
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsWebResource.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsWebResource.java
new file mode 100644
index 0000000..4c61973
--- /dev/null
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsWebResource.java
@@ -0,0 +1,275 @@
+/*
+ * 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;
+    }
+}
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/SystemMetricsCollectorWebResource.java
similarity index 95%
rename from apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java
rename to apps/cpman/app/src/main/java/org/onosproject/cpman/rest/SystemMetricsCollectorWebResource.java
index 07412861..6a6e50f 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/SystemMetricsCollectorWebResource.java
@@ -40,16 +40,16 @@
 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.
diff --git a/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java b/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java
new file mode 100644
index 0000000..f8d6c7d
--- /dev/null
+++ b/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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\":[]}}]}"));
+    }
+}