Add uptimes to device and cluster REST APIs

Change-Id: I0ccdf4e33135be4bcfd1674a76ff4b39e992268b
diff --git a/cli/src/main/java/org/onosproject/cli/NodesListCommand.java b/cli/src/main/java/org/onosproject/cli/NodesListCommand.java
index 4e49fe9..ab1216e 100644
--- a/cli/src/main/java/org/onosproject/cli/NodesListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/NodesListCommand.java
@@ -20,13 +20,11 @@
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
-import org.onlab.util.Tools;
 import org.onosproject.cluster.ClusterAdminService;
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.core.Version;
 import org.onosproject.utils.Comparators;
 
-import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
 
@@ -51,11 +49,7 @@
         } else {
             ControllerNode self = service.getLocalNode();
             for (ControllerNode node : nodes) {
-                Instant lastUpdated = service.getLastUpdatedInstant(node.id());
-                String timeAgo = "Never";
-                if (lastUpdated != null) {
-                    timeAgo = Tools.timeAgo(lastUpdated.toEpochMilli());
-                }
+                String timeAgo = service.localStatus(node.id());
                 Version version = service.getVersion(node.id());
                 print(FMT, node.id(), node.ip(), node.tcpPort(),
                         service.getState(node.id()),
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterService.java b/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
index 1a75e5f..54f23cd 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
@@ -20,6 +20,7 @@
 import java.util.Set;
 
 import org.joda.time.DateTime;
+import org.onlab.util.Tools;
 import org.onosproject.core.Version;
 import org.onosproject.event.ListenerService;
 
@@ -82,6 +83,21 @@
     }
 
     /**
+     * Returns a human readable form of the system time when the availability state was last updated.
+     *
+     * @param nodeId controller node identifier
+     * @return human readable string for system time when the availability state was last updated.
+     */
+    default String localStatus(NodeId nodeId) {
+        Instant lastUpdated = getLastUpdatedInstant(nodeId);
+        String timeAgo = "Never";
+        if (lastUpdated != null) {
+            timeAgo = Tools.timeAgo(lastUpdated.toEpochMilli());
+        }
+        return timeAgo;
+    }
+
+    /**
      * Returns the system time when the availability state was last updated.
      *
      * @param nodeId controller node identifier
diff --git a/core/api/src/main/java/org/onosproject/net/device/DeviceService.java b/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
index 232809b..f86a98a 100644
--- a/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
+++ b/core/api/src/main/java/org/onosproject/net/device/DeviceService.java
@@ -185,4 +185,14 @@
      */
     String localStatus(DeviceId deviceId);
 
+
+    /**
+     * Indicates how long ago the device connected or disconnected from this
+     * controller instance as a time offset.
+     *
+     * @param deviceId device identifier
+     * @return time offset in miliseconds
+     */
+    long getLastUpdatedInstant(DeviceId deviceId);
+
 }
diff --git a/core/api/src/main/java/org/onosproject/net/device/DeviceServiceAdapter.java b/core/api/src/main/java/org/onosproject/net/device/DeviceServiceAdapter.java
index 125cbcc..f85d198 100644
--- a/core/api/src/main/java/org/onosproject/net/device/DeviceServiceAdapter.java
+++ b/core/api/src/main/java/org/onosproject/net/device/DeviceServiceAdapter.java
@@ -142,4 +142,9 @@
         return null;
     }
 
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        return 0;
+    }
+
 }
diff --git a/core/api/src/main/java/org/onosproject/net/utils/ForwardingDeviceService.java b/core/api/src/main/java/org/onosproject/net/utils/ForwardingDeviceService.java
index acca07f..3a3eea2 100644
--- a/core/api/src/main/java/org/onosproject/net/utils/ForwardingDeviceService.java
+++ b/core/api/src/main/java/org/onosproject/net/utils/ForwardingDeviceService.java
@@ -127,4 +127,9 @@
         return delegate.localStatus(deviceId);
     }
 
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        return delegate.getLastUpdatedInstant(deviceId);
+    }
+
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
index f6206b9..07d8e45 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
@@ -40,7 +40,9 @@
                 .put("id", node.id().toString())
                 .put("ip", node.ip().toString())
                 .put("tcpPort", node.tcpPort())
-                .put("status", service.getState(node.id()).toString());
+                .put("status", service.getState(node.id()).toString())
+                .put("lastUpdate", Long.toString(service.getLastUpdatedInstant(node.id()).toEpochMilli()))
+                .put("humanReadableLastUpdate", service.localStatus(node.id()));
     }
 
 
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
index 029549e..d34092a 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
@@ -47,7 +47,8 @@
     private static final String SERIAL = "serial";
     private static final String CHASSIS_ID = "chassisId";
     private static final String DRIVER = "driver";
-
+    private static final String LAST_UPDATE = "lastUpdate";
+    private static final String HUMAN_READABLE_LAST_UPDATE = "humanReadableLastUpdate";
 
     @Override
     public ObjectNode encode(Device device, CodecContext context) {
@@ -64,7 +65,9 @@
                 .put(SW, device.swVersion())
                 .put(SERIAL, device.serialNumber())
                 .put(DRIVER, driveService.getDriver(device.id()).name())
-                .put(CHASSIS_ID, device.chassisId().toString());
+                .put(CHASSIS_ID, device.chassisId().toString())
+                .put(LAST_UPDATE, Long.toString(service.getLastUpdatedInstant(device.id())))
+                .put(HUMAN_READABLE_LAST_UPDATE, service.localStatus(device.id()));
         return annotate(result, device, context);
     }
 
diff --git a/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
index c82c1f5..9f5040a 100644
--- a/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
@@ -344,6 +344,15 @@
         return (ls.connected) ? "connected " + timeAgo : "disconnected " + timeAgo;
     }
 
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        LocalStatus ls = deviceLocalStatus.get(deviceId);
+        if (ls == null) {
+            return 0;
+        }
+        return ls.dateTime.toEpochMilli();
+    }
+
     // Check a device for control channel connectivity.
     private boolean isReachable(DeviceId deviceId) {
         if (deviceId == null) {
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
index 3f808b4..601a05d 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkDeviceManager.java
@@ -182,6 +182,12 @@
         return null;
     }
 
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        // TODO not supported at this time
+        return 0;
+    }
+
     /**
      * Translates VirtualNetworkEvent to DeviceEvent.
      */
diff --git a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfDeviceServiceMock.java b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfDeviceServiceMock.java
index 3e9a614..f89df2b 100644
--- a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfDeviceServiceMock.java
+++ b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfDeviceServiceMock.java
@@ -116,4 +116,9 @@
     public String localStatus(DeviceId deviceId) {
         return null;
     }
+
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        return 0;
+    }
 }
\ No newline at end of file
diff --git a/protocols/pcep/server/ctl/src/test/java/org/onosproject/pcelabelstore/util/MockDeviceService.java b/protocols/pcep/server/ctl/src/test/java/org/onosproject/pcelabelstore/util/MockDeviceService.java
index 571dd01..5ff1f54 100644
--- a/protocols/pcep/server/ctl/src/test/java/org/onosproject/pcelabelstore/util/MockDeviceService.java
+++ b/protocols/pcep/server/ctl/src/test/java/org/onosproject/pcelabelstore/util/MockDeviceService.java
@@ -151,4 +151,9 @@
         // TODO Auto-generated method stub
         return null;
     }
+
+    @Override
+    public long getLastUpdatedInstant(DeviceId deviceId) {
+        return 0;
+    }
 }
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/DevicesResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/DevicesResourceTest.java
index 75d33a8..19fb782 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/DevicesResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/DevicesResourceTest.java
@@ -173,8 +173,8 @@
 
         @Override
         public boolean matchesSafely(JsonArray json) {
-            final int minExpectedAttributes = 9;
-            final int maxExpectedAttributes = 10;
+            final int minExpectedAttributes = 11;
+            final int maxExpectedAttributes = 12;
 
             boolean deviceFound = false;
 
@@ -234,6 +234,12 @@
         expect(mockDeviceService.getRole(isA(DeviceId.class)))
                 .andReturn(MastershipRole.MASTER)
                 .anyTimes();
+        expect(mockDeviceService.getLastUpdatedInstant(isA(DeviceId.class)))
+                .andReturn(0L)
+                .anyTimes();
+        expect(mockDeviceService.localStatus(isA(DeviceId.class)))
+                .andReturn("")
+                .anyTimes();
 
 
         // Register the services needed for the test