[SDFAB-199] Add the support for last-change

- Add parsing in OpenConfigGnmiDeviceDescriptionDiscovery and
  defaults to 0 for the devices not providing last-change
- Remove hack in OpenConfigGnmiPortStatisticsDiscovery and set
  the duration to 0 for the devices that do not support last-change
- Subscribe to the state changes of a given port and add parsing of
  last-change timestamp in GnmiDeviceStateSubscribe

Note that if the device does not aggregate updates into a single notification
two PORT_UPDATED events will be generated. The first as consequence of the
operational status change and the second caused by the reconciliation which
updates correctly last-change

Change-Id: I6b2cb3652b306358bd9e701780946864a1ed324b
diff --git a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GnmiDeviceStateSubscriber.java b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GnmiDeviceStateSubscriber.java
index b7804bb..d55ca7f 100644
--- a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GnmiDeviceStateSubscriber.java
+++ b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GnmiDeviceStateSubscriber.java
@@ -62,7 +62,7 @@
 @Beta
 class GnmiDeviceStateSubscriber {
 
-    private static final String LAST_CHANGE = "last-changed";
+    private static final String LAST_CHANGE = "last-change";
 
     private static Logger log = LoggerFactory.getLogger(GnmiDeviceStateSubscriber.class);
 
@@ -126,13 +126,12 @@
                 && !deviceService.getPorts(deviceId).isEmpty();
     }
 
-    private Path interfaceOperStatusPath(String interfaceName) {
+    private Path interfaceStatePath(String interfaceName) {
         return Path.newBuilder()
                 .addElem(PathElem.newBuilder().setName("interfaces").build())
                 .addElem(PathElem.newBuilder()
                                  .setName("interface").putKey("name", interfaceName).build())
                 .addElem(PathElem.newBuilder().setName("state").build())
-                .addElem(PathElem.newBuilder().setName("oper-status").build())
                 .build();
     }
 
@@ -163,7 +162,7 @@
                 .setUpdatesOnly(true)
                 .addAllSubscription(ports.stream().map(
                         port -> Subscription.newBuilder()
-                                .setPath(interfaceOperStatusPath(port.name()))
+                                .setPath(interfaceStatePath(port.name()))
                                 .setMode(SubscriptionMode.ON_CHANGE)
                                 .build()).collect(Collectors.toList()))
                 .build();
@@ -183,19 +182,32 @@
             return;
         }
 
-        List<Update> updateList = notification.getUpdateList();
-        updateList.forEach(update -> {
-            Path path = update.getPath();
-            PathElem lastElem = path.getElem(path.getElemCount() - 1);
+        long lastChange = 0;
+        Update statusUpdate = null;
+        Path path;
+        PathElem lastElem;
+        // The assumption is that the notification contains all the updates:
+        // last-change, oper-status, counters, and so on. Otherwise, we need
+        // to put in place the aggregation logic in ONOS
+        for (Update update : notification.getUpdateList()) {
+            path = update.getPath();
+            lastElem = path.getElem(path.getElemCount() - 1);
 
             // Use last element to identify which state updated
             if ("oper-status".equals(lastElem.getName())) {
-                handleOperStatusUpdate(eventSubject.deviceId(), update,
-                                       notification.getTimestamp());
-            } else {
+                statusUpdate = update;
+            } else if ("last-change".equals(lastElem.getName())) {
+                lastChange = update.getVal().getUintVal();
+            } else if (log.isDebugEnabled()) {
                 log.debug("Unrecognized update {}", GnmiUtils.pathToString(path));
             }
-        });
+        }
+
+        // Last-change could be not supported by the device
+        // Cannot proceed without the status update.
+        if (statusUpdate != null) {
+            handleOperStatusUpdate(eventSubject.deviceId(), statusUpdate, lastChange);
+        }
     }
 
     private void handleOperStatusUpdate(DeviceId deviceId, Update update, long timestamp) {