ONOS-400 Topology creation and up time formatting fixes

Change-Id: Iaf6d4dbbc1c7eaae9465a2d931d40f07a75ad07d
diff --git a/cli/src/main/java/org/onosproject/cli/net/TopologyCommand.java b/cli/src/main/java/org/onosproject/cli/net/TopologyCommand.java
index 337a1f7..09682a2 100644
--- a/cli/src/main/java/org/onosproject/cli/net/TopologyCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/TopologyCommand.java
@@ -15,7 +15,11 @@
  */
 package org.onosproject.cli.net;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
 import org.onosproject.cli.AbstractShellCommand;
@@ -23,18 +27,20 @@
 import org.onosproject.net.topology.TopologyProvider;
 import org.onosproject.net.topology.TopologyService;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 /**
  * Lists summary of the current topology.
  */
 @Command(scope = "onos", name = "topology",
-         description = "Lists summary of the current topology")
+description = "Lists summary of the current topology")
 public class TopologyCommand extends AbstractShellCommand {
 
-    private static final String FMT =
-            "time=%s, devices=%d, links=%d, clusters=%d";
+    private static final String FMT = "created=%s, uptime=%s, devices=%d, links=%d, clusters=%d";
 
-    @Option(name = "-r", aliases = "--recompute", description = "Trigger topology re-computation",
-            required = false, multiValued = false)
+    @Option(name = "-r", aliases = "--recompute",
+            description = "Trigger topology re-computation", required = false,
+            multiValued = false)
     private boolean recompute = false;
 
     protected TopologyService service;
@@ -51,19 +57,75 @@
     @Override
     protected void execute() {
         init();
+        long topologyUptime =
+                Math.max(0, (System.currentTimeMillis() - topology.creationTime()));
         if (recompute) {
             get(TopologyProvider.class).triggerRecompute();
 
         } else if (outputJson()) {
-            print("%s", new ObjectMapper().createObjectNode()
-                    .put("time", topology.time())
-                    .put("deviceCount", topology.deviceCount())
-                    .put("linkCount", topology.linkCount())
-                    .put("clusterCount", topology.clusterCount()));
+            print("%s",
+                    new ObjectMapper()
+                            .createObjectNode()
+                            .put("time", topology.time())
+                            .put("created", formatCreationTime(topology.creationTime()))
+                            .put("uptime", formatElapsedTime(topologyUptime))
+                            .put("deviceCount", topology.deviceCount())
+                            .put("linkCount", topology.linkCount())
+                            .put("clusterCount", topology.clusterCount()));
         } else {
-            print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(),
-                  topology.clusterCount());
+            print(FMT, formatCreationTime(topology.creationTime()),
+                    formatElapsedTime(topologyUptime),
+                    topology.deviceCount(), topology.linkCount(),
+                    topology.clusterCount());
         }
     }
 
+    /**
+     * Converts millis to a formatted elapsed time string.
+     *
+     * @param millis Duration in millis to convert to a string
+     *
+     * @return Formatted string: "D days, H hrs, M mins, S secs".
+     */
+    private static String formatElapsedTime(long millis) {
+        if (millis < 0) {
+            throw new IllegalArgumentException("Interval less than zero. "
+                    + "Possible unsynchronized timestamps");
+        }
+
+        final long days = TimeUnit.MILLISECONDS.toDays(millis);
+        millis -= TimeUnit.DAYS.toMillis(days);
+        final long hours = TimeUnit.MILLISECONDS.toHours(millis);
+        millis -= TimeUnit.HOURS.toMillis(hours);
+        final long minutes = TimeUnit.MILLISECONDS.toMinutes(millis);
+        millis -= TimeUnit.MINUTES.toMillis(minutes);
+        final long seconds = TimeUnit.MILLISECONDS.toSeconds(millis);
+
+        final StringBuilder topologyUptimeString = new StringBuilder(64);
+        topologyUptimeString.append(days);
+        topologyUptimeString.append(" days, ");
+        topologyUptimeString.append(hours);
+        topologyUptimeString.append(" hrs, ");
+        topologyUptimeString.append(minutes);
+        topologyUptimeString.append(" mins, ");
+        topologyUptimeString.append(seconds);
+        topologyUptimeString.append(" secs");
+
+        return (topologyUptimeString.toString());
+    }
+
+    /**
+     * Converts millis to a formatted Date String.
+     *
+     * @param millis Duration in millis to convert to a string
+     *
+     * @return Formatted string: yyyy-MM-dd HH:mm:ss.
+     */
+    private static String formatCreationTime(long millis) {
+        final DateFormat dateFormatter =
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(millis);
+        return (dateFormatter.format(calendar.getTime()));
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java b/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java
index 4d60e23..08db92f 100644
--- a/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/topology/DefaultGraphDescription.java
@@ -34,30 +34,60 @@
  * Default implementation of an immutable topology graph data carrier.
  */
 public class DefaultGraphDescription extends AbstractDescription
-        implements GraphDescription {
+implements GraphDescription {
 
     private static final Logger log = getLogger(DefaultGraphDescription.class);
 
     private final long nanos;
+    private final long creationTime;
     private final ImmutableSet<TopologyVertex> vertexes;
     private final ImmutableSet<TopologyEdge> edges;
 
-    private final Map<DeviceId, TopologyVertex> vertexesById = Maps.newHashMap();
+    private final Map<DeviceId, TopologyVertex> vertexesById = Maps
+            .newHashMap();
 
     /**
      * Creates a minimal topology graph description to allow core to construct
      * and process the topology graph.
      *
-     * @param nanos       time in nanos of when the topology description was created
-     * @param devices     collection of infrastructure devices
-     * @param links       collection of infrastructure links
+     * @param nanos time in nanos of when the topology description was created
+     *
+     * @param devices collection of infrastructure devices
+     *
+     * @param links collection of infrastructure links
+     *
      * @param annotations optional key/value annotations map
+     *
      */
+    @Deprecated
     public DefaultGraphDescription(long nanos, Iterable<Device> devices,
-                                   Iterable<Link> links,
-                                   SparseAnnotations... annotations) {
+            Iterable<Link> links,
+            SparseAnnotations... annotations) {
+        this(nanos, System.currentTimeMillis(), devices, links, annotations);
+    }
+
+    /**
+     * Creates a minimal topology graph description to allow core to construct
+     * and process the topology graph.
+     *
+     * @param nanos time in nanos of when the topology description was created
+     *
+     * @param millis time in millis of when the topology description was created
+     *
+     * @param devices collection of infrastructure devices
+     *
+     * @param links collection of infrastructure links
+     *
+     * @param annotations optional key/value annotations map
+     *
+     */
+    public DefaultGraphDescription(long nanos, long millis,
+            Iterable<Device> devices,
+            Iterable<Link> links,
+            SparseAnnotations... annotations) {
         super(annotations);
         this.nanos = nanos;
+        this.creationTime = millis;
         this.vertexes = buildVertexes(devices);
         this.edges = buildEdges(links);
         vertexesById.clear();
@@ -69,6 +99,11 @@
     }
 
     @Override
+    public long creationTime() {
+        return creationTime;
+    }
+
+    @Override
     public ImmutableSet<TopologyVertex> vertexes() {
         return vertexes;
     }
@@ -79,7 +114,8 @@
     }
 
     // Builds a set of topology vertexes from the specified list of devices
-    private ImmutableSet<TopologyVertex> buildVertexes(Iterable<Device> devices) {
+    private ImmutableSet<TopologyVertex>
+            buildVertexes(Iterable<Device> devices) {
         ImmutableSet.Builder<TopologyVertex> vertexes = ImmutableSet.builder();
         for (Device device : devices) {
             TopologyVertex vertex = new DefaultTopologyVertex(device.id());
@@ -95,7 +131,8 @@
         for (Link link : links) {
             try {
                 edges.add(new DefaultTopologyEdge(vertexOf(link.src()),
-                                                  vertexOf(link.dst()), link));
+                                                  vertexOf(link.dst()),
+                                                  link));
             } catch (IllegalArgumentException e) {
                 log.debug("Ignoring {}, missing vertex", link);
             }
diff --git a/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java b/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java
index 3a243df..cc22c33 100644
--- a/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/topology/GraphDescription.java
@@ -15,9 +15,10 @@
  */
 package org.onosproject.net.topology;
 
-import com.google.common.collect.ImmutableSet;
 import org.onosproject.net.Description;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * Describes attribute(s) of a network graph.
  */
@@ -32,6 +33,14 @@
     long timestamp();
 
     /**
+     * Returns the creation timestamp of the graph description. This is
+     * expressed in system millis to allow proper date and time formatting.
+     *
+     * @return graph description creation timestamp in millis
+     */
+    long creationTime();
+
+    /**
      * Returns the set of topology graph vertexes.
      *
      * @return set of graph vertexes
@@ -46,4 +55,3 @@
     ImmutableSet<TopologyEdge> edges();
 
 }
-
diff --git a/core/api/src/main/java/org/onosproject/net/topology/Topology.java b/core/api/src/main/java/org/onosproject/net/topology/Topology.java
index df8d63c..a174219 100644
--- a/core/api/src/main/java/org/onosproject/net/topology/Topology.java
+++ b/core/api/src/main/java/org/onosproject/net/topology/Topology.java
@@ -23,16 +23,24 @@
 public interface Topology extends Provided {
 
     /**
-     * Returns the time, specified in system nanos of when the topology
-     * became available.
+     * Returns the time, specified in system nanos of when the topology became
+     * available.
      *
      * @return time in system nanos
      */
     long time();
 
     /**
-     * Returns the time, specified in system nanos of how long the topology
-     * took to compute.
+     * Returns the time, specified in system millis of when the topology became
+     * available.
+     *
+     * @return time in system nanos
+     */
+    long creationTime();
+
+    /**
+     * Returns the time, specified in system nanos of how long the topology took
+     * to compute.
      *
      * @return elapsed time in system nanos
      */
@@ -53,7 +61,6 @@
      */
     int deviceCount();
 
-
     /**
      * Returns the number of infrastructure links in the topology.
      *
diff --git a/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java b/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java
index a331d50..af40595 100644
--- a/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java
+++ b/core/net/src/main/java/org/onosproject/net/topology/impl/DefaultTopologyProvider.java
@@ -15,7 +15,21 @@
  */
 package org.onosproject.net.topology.impl;
 
-import com.google.common.collect.ImmutableList;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Timer;
+import java.util.concurrent.ExecutorService;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -42,28 +56,16 @@
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.Timer;
-import java.util.concurrent.ExecutorService;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.util.concurrent.Executors.newFixedThreadPool;
-import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
-import static org.onosproject.net.device.DeviceEvent.Type.*;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.ImmutableList;
 
 /**
- * Default implementation of a network topology provider that feeds off
- * device and link subsystem events to trigger assembly and computation of
- * new topology snapshots.
+ * Default implementation of a network topology provider that feeds off device
+ * and link subsystem events to trigger assembly and computation of new topology
+ * snapshots.
  */
 @Component(immediate = true)
 @Service
-public class DefaultTopologyProvider extends AbstractProvider
-        implements TopologyProvider {
+public class DefaultTopologyProvider extends AbstractProvider implements TopologyProvider {
 
     private static final int MAX_THREADS = 8;
     private static final int DEFAULT_MAX_EVENTS = 1000;
@@ -71,7 +73,8 @@
     private static final int DEFAULT_MAX_BATCH_MS = 50;
 
     // FIXME: Replace with a system-wide timer instance;
-    // TODO: Convert to use HashedWheelTimer or produce a variant of that; then decide which we want to adopt
+    // TODO: Convert to use HashedWheelTimer or produce a variant of that; then
+    // decide which we want to adopt
     private static final Timer TIMER = new Timer("onos-topo-event-batching");
 
     @Property(name = "maxEvents", intValue = DEFAULT_MAX_EVENTS,
@@ -100,8 +103,8 @@
     private volatile boolean isStarted = false;
 
     private TopologyProviderService providerService;
-    private DeviceListener deviceListener = new InternalDeviceListener();
-    private LinkListener linkListener = new InternalLinkListener();
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+    private final LinkListener linkListener = new InternalLinkListener();
 
     private Accumulator<Event> accumulator;
     private ExecutorService executor;
@@ -115,7 +118,8 @@
 
     @Activate
     public synchronized void activate(ComponentContext context) {
-        executor = newFixedThreadPool(MAX_THREADS, groupedThreads("onos/topo", "build-%d"));
+        executor = newFixedThreadPool(MAX_THREADS,
+                groupedThreads("onos/topo", "build-%d"));
         accumulator = new TopologyChangeAccumulator();
         logConfig("Configured");
 
@@ -171,21 +175,23 @@
             newMaxIdleMs = DEFAULT_MAX_IDLE_MS;
         }
 
-        if (newMaxEvents != maxEvents || newMaxBatchMs != maxBatchMs || newMaxIdleMs != maxIdleMs) {
+        if ((newMaxEvents != maxEvents) || (newMaxBatchMs != maxBatchMs)
+                || (newMaxIdleMs != maxIdleMs)) {
             maxEvents = newMaxEvents;
             maxBatchMs = newMaxBatchMs;
             maxIdleMs = newMaxIdleMs;
-            accumulator = maxEvents > 1 ? new TopologyChangeAccumulator() : null;
+            accumulator = maxEvents > 1 ? new TopologyChangeAccumulator()
+            : null;
             logConfig("Reconfigured");
         }
     }
 
     private void logConfig(String prefix) {
-        log.info("{} with maxEvents = {}; maxBatchMs = {}; maxIdleMs = {}; accumulator={}",
-                 prefix, maxEvents, maxBatchMs, maxIdleMs, accumulator != null);
+        log.info(
+                "{} with maxEvents = {}; maxBatchMs = {}; maxIdleMs = {}; accumulator={}",
+                prefix, maxEvents, maxBatchMs, maxIdleMs, accumulator != null);
     }
 
-
     @Override
     public void triggerRecompute() {
         triggerTopologyBuild(Collections.<Event>emptyList());
@@ -195,7 +201,8 @@
      * Triggers assembly of topology data citing the specified events as the
      * reason.
      *
-     * @param reasons events which triggered the topology change
+     * @param reasons
+     *            events which triggered the topology change
      */
     private synchronized void triggerTopologyBuild(List<Event> reasons) {
         if (executor != null) {
@@ -209,8 +216,9 @@
         if (isStarted) {
             GraphDescription desc =
                     new DefaultGraphDescription(System.nanoTime(),
-                                                deviceService.getAvailableDevices(),
-                                                linkService.getActiveLinks());
+                            System.currentTimeMillis(),
+                            deviceService.getAvailableDevices(),
+                            linkService.getActiveLinks());
             providerService.topologyChanged(desc, reasons);
         }
     }
@@ -228,8 +236,8 @@
         @Override
         public void event(DeviceEvent event) {
             DeviceEvent.Type type = event.type();
-            if (type == DEVICE_ADDED || type == DEVICE_REMOVED ||
-                    type == DEVICE_AVAILABILITY_CHANGED) {
+            if ((type == DEVICE_ADDED) || (type == DEVICE_REMOVED) ||
+                    (type == DEVICE_AVAILABILITY_CHANGED)) {
                 processEvent(event);
             }
         }
@@ -268,7 +276,8 @@
             try {
                 buildTopology(reasons);
             } catch (Exception e) {
-                log.warn("Unable to compute topology due to: {}", e.getMessage());
+                log.warn("Unable to compute topology due to: {}",
+                        e.getMessage());
                 log.debug("Unable to compute topology", e);
             }
         }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DefaultTopology.java b/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DefaultTopology.java
index 8c15e45..e597449 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DefaultTopology.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/topology/impl/DefaultTopology.java
@@ -15,11 +15,18 @@
  */
 package org.onosproject.store.topology.impl;
 
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
+import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.State.INACTIVE;
+import static org.onosproject.net.Link.Type.INDIRECT;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import org.onlab.graph.DijkstraGraphSearch;
 import org.onlab.graph.GraphPathSearch;
 import org.onlab.graph.GraphPathSearch.Result;
@@ -43,32 +50,25 @@
 import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.net.topology.TopologyVertex;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-import static com.google.common.collect.ImmutableSetMultimap.Builder;
-import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
-import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
-import static org.onosproject.net.Link.State.ACTIVE;
-import static org.onosproject.net.Link.State.INACTIVE;
-import static org.onosproject.net.Link.Type.INDIRECT;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSetMultimap.Builder;
 
 // FIXME: Move to onos-core-common when ready
 /**
- * Default implementation of the topology descriptor. This carries the
- * backing topology data.
+ * Default implementation of the topology descriptor. This carries the backing
+ * topology data.
  */
 public class DefaultTopology extends AbstractModel implements Topology {
 
-    private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA =
-            new DijkstraGraphSearch<>();
-    private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN =
-            new TarjanGraphSearch<>();
+    private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA = new DijkstraGraphSearch<>();
+    private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN = new TarjanGraphSearch<>();
 
     private final long time;
+    private final long creationTime;
     private final long computeCost;
     private final TopologyGraph graph;
 
@@ -83,16 +83,19 @@
     /**
      * Creates a topology descriptor attributed to the specified provider.
      *
-     * @param providerId  identity of the provider
-     * @param description data describing the new topology
+     * @param providerId
+     *            identity of the provider
+     * @param description
+     *            data describing the new topology
      */
     DefaultTopology(ProviderId providerId, GraphDescription description) {
         super(providerId);
         this.time = description.timestamp();
+        this.creationTime = description.creationTime();
 
         // Build the graph
         this.graph = new DefaultTopologyGraph(description.vertexes(),
-                                              description.edges());
+                description.edges());
 
         this.clusterResults = Suppliers.memoize(() -> searchForClusters());
         this.clusters = Suppliers.memoize(() -> buildTopologyClusters());
@@ -101,7 +104,8 @@
 
         this.weight = new HopCountLinkWeight(graph.getVertexes().size());
         this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
-        this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
+        this.infrastructurePoints = Suppliers
+                .memoize(() -> findInfrastructurePoints());
         this.computeCost = Math.max(0, System.nanoTime() - time);
     }
 
@@ -111,6 +115,11 @@
     }
 
     @Override
+    public long creationTime() {
+        return creationTime;
+    }
+
+    @Override
     public long computeCost() {
         return computeCost;
     }
@@ -164,6 +173,7 @@
      * Returns the specified topology cluster.
      *
      * @param clusterId cluster identifier
+     *
      * @return topology cluster
      */
     TopologyCluster getCluster(ClusterId clusterId) {
@@ -174,6 +184,7 @@
      * Returns the topology cluster that contains the given device.
      *
      * @param deviceId device identifier
+     *
      * @return topology cluster
      */
     TopologyCluster getCluster(DeviceId deviceId) {
@@ -184,6 +195,7 @@
      * Returns the set of cluster devices.
      *
      * @param cluster topology cluster
+     *
      * @return cluster devices
      */
     Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
@@ -194,6 +206,7 @@
      * Returns the set of cluster links.
      *
      * @param cluster topology cluster
+     *
      * @return cluster links
      */
     Set<Link> getClusterLinks(TopologyCluster cluster) {
@@ -204,6 +217,7 @@
      * Indicates whether the given point is an infrastructure link end-point.
      *
      * @param connectPoint connection point
+     *
      * @return true if infrastructure
      */
     boolean isInfrastructure(ConnectPoint connectPoint) {
@@ -214,6 +228,7 @@
      * Indicates whether the given point is part of a broadcast set.
      *
      * @param connectPoint connection point
+     *
      * @return true if in broadcast set
      */
     boolean isBroadcastPoint(ConnectPoint connectPoint) {
@@ -225,19 +240,21 @@
         // Find the cluster to which the device belongs.
         TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId());
         if (cluster == null) {
-            throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
+            throw new IllegalArgumentException("No cluster found for device "
+                    + connectPoint.deviceId());
         }
 
         // If the broadcast set is null or empty, or if the point explicitly
         // belongs to it, return true;
         Set<ConnectPoint> points = broadcastSets.get().get(cluster.id());
-        return points == null || points.isEmpty() || points.contains(connectPoint);
+        return (points == null) || points.isEmpty() || points.contains(connectPoint);
     }
 
     /**
      * Returns the size of the cluster broadcast set.
      *
      * @param clusterId cluster identifier
+     *
      * @return size of the cluster broadcast set
      */
     int broadcastSetSize(ClusterId clusterId) {
@@ -249,7 +266,9 @@
      * destination devices.
      *
      * @param src source device
+     *
      * @param dst destination device
+     *
      * @return set of shortest paths
      */
     Set<Path> getPaths(DeviceId src, DeviceId dst) {
@@ -260,9 +279,12 @@
      * Computes on-demand the set of shortest paths between source and
      * destination devices.
      *
-     * @param src    source device
-     * @param dst    destination device
+     * @param src source device
+     *
+     * @param dst destination device
+     *
      * @param weight link weight function
+     *
      * @return set of shortest paths
      */
     Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
@@ -283,7 +305,6 @@
         return builder.build();
     }
 
-
     // Converts graph path to a network path with the same cost.
     private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
         List<Link> links = new ArrayList<>();
@@ -293,7 +314,6 @@
         return new DefaultPath(CORE_PROVIDER_ID, links, path.cost());
     }
 
-
     // Searches for SCC clusters in the network topology graph using Tarjan
     // algorithm.
     private SCCResult<TopologyVertex, TopologyEdge> searchForClusters() {
@@ -315,9 +335,10 @@
             Set<TopologyEdge> edgeSet = clusterEdges.get(i);
 
             ClusterId cid = ClusterId.clusterId(i);
-            DefaultTopologyCluster cluster =
-                    new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
-                                               findRoot(vertexSet));
+            DefaultTopologyCluster cluster = new DefaultTopologyCluster(cid,
+                                                                        vertexSet.size(),
+                                                                        edgeSet.size(),
+                                                                        findRoot(vertexSet));
             clusterBuilder.put(cid, cluster);
         }
         return clusterBuilder.build();
@@ -328,9 +349,8 @@
     private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
         TopologyVertex minVertex = null;
         for (TopologyVertex vertex : vertexSet) {
-            if (minVertex == null ||
-                    minVertex.deviceId().toString()
-                            .compareTo(minVertex.deviceId().toString()) < 0) {
+            if ((minVertex == null) || (minVertex.deviceId()
+                    .toString().compareTo(minVertex.deviceId().toString()) < 0)) {
                 minVertex = vertex;
             }
         }
@@ -339,7 +359,8 @@
 
     // Processes a map of broadcast sets for each cluster.
     private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
-        Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
+        Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap
+                .builder();
         for (TopologyCluster cluster : clusters.get().values()) {
             addClusterBroadcastSet(cluster, builder);
         }
@@ -349,11 +370,9 @@
     // Finds all broadcast points for the cluster. These are those connection
     // points which lie along the shortest paths between the cluster root and
     // all other devices within the cluster.
-    private void addClusterBroadcastSet(TopologyCluster cluster,
-                                        Builder<ClusterId, ConnectPoint> builder) {
+    private void addClusterBroadcastSet(TopologyCluster cluster, Builder<ClusterId, ConnectPoint> builder) {
         // Use the graph root search results to build the broadcast set.
-        Result<TopologyVertex, TopologyEdge> result =
-                DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
+        Result<TopologyVertex, TopologyEdge> result = DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
         for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
             TopologyVertex vertex = entry.getKey();
 
@@ -389,9 +408,12 @@
     // Builds cluster-devices, cluster-links and device-cluster indexes.
     private ClusterIndexes buildIndexes() {
         // Prepare the index builders
-        ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
-        ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
-        ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
+        ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder =
+                ImmutableMap.builder();
+        ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder =
+                ImmutableSetMultimap.builder();
+        ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder =
+                ImmutableSetMultimap.builder();
 
         // Now scan through all the clusters
         for (TopologyCluster cluster : clusters.get().values()) {
@@ -428,8 +450,9 @@
         public double weight(TopologyEdge edge) {
             // To force preference to use direct paths first, make indirect
             // links as expensive as the linear vertex traversal.
-            return edge.link().state() == ACTIVE ?
-                    (edge.link().type() == INDIRECT ? indirectLinkCost : 1) : -1;
+            return edge.link().state() ==
+                    ACTIVE ? (edge.link().type() ==
+                    INDIRECT ? indirectLinkCost : 1) : -1;
         }
     }
 
@@ -437,7 +460,8 @@
     private static class NoIndirectLinksWeight implements LinkWeight {
         @Override
         public double weight(TopologyEdge edge) {
-            return edge.link().state() == INACTIVE || edge.link().type() == INDIRECT ? -1 : 1;
+            return (edge.link().state() == INACTIVE)
+                    || (edge.link().type() == INDIRECT) ? -1 : 1;
         }
     }
 
@@ -446,9 +470,10 @@
         final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
         final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
 
-        public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
-                              ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
-                              ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
+        public ClusterIndexes(
+                ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
+                ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
+                ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
             this.clustersByDevice = clustersByDevice;
             this.devicesByCluster = devicesByCluster;
             this.linksByCluster = linksByCluster;
@@ -459,10 +484,10 @@
     public String toString() {
         return toStringHelper(this)
                 .add("time", time)
+                .add("creationTime", creationTime)
                 .add("computeCost", computeCost)
                 .add("clusters", clusterCount())
                 .add("devices", deviceCount())
-                .add("links", linkCount())
-                .toString();
+                .add("links", linkCount()).toString();
     }
 }
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/DefaultTopology.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/DefaultTopology.java
index 8dea38b..2b23364 100644
--- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/DefaultTopology.java
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/DefaultTopology.java
@@ -15,11 +15,18 @@
  */
 package org.onosproject.store.trivial.impl;
 
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
+import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.State.INACTIVE;
+import static org.onosproject.net.Link.Type.INDIRECT;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import org.onlab.graph.DijkstraGraphSearch;
 import org.onlab.graph.GraphPathSearch;
 import org.onlab.graph.GraphPathSearch.Result;
@@ -43,32 +50,25 @@
 import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.net.topology.TopologyVertex;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-import static com.google.common.collect.ImmutableSetMultimap.Builder;
-import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
-import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
-import static org.onosproject.net.Link.State.ACTIVE;
-import static org.onosproject.net.Link.State.INACTIVE;
-import static org.onosproject.net.Link.Type.INDIRECT;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSetMultimap.Builder;
 
 // FIXME: Move to onos-core-common when ready
 /**
- * Default implementation of the topology descriptor. This carries the
- * backing topology data.
+ * Default implementation of the topology descriptor. This carries the backing
+ * topology data.
  */
 public class DefaultTopology extends AbstractModel implements Topology {
 
-    private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA =
-            new DijkstraGraphSearch<>();
-    private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN =
-            new TarjanGraphSearch<>();
+    private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA = new DijkstraGraphSearch<>();
+    private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN = new TarjanGraphSearch<>();
 
     private final long time;
+    private final long creationTime;
     private final long computeCost;
     private final TopologyGraph graph;
 
@@ -83,16 +83,19 @@
     /**
      * Creates a topology descriptor attributed to the specified provider.
      *
-     * @param providerId  identity of the provider
-     * @param description data describing the new topology
+     * @param providerId
+     *            identity of the provider
+     * @param description
+     *            data describing the new topology
      */
     DefaultTopology(ProviderId providerId, GraphDescription description) {
         super(providerId);
         this.time = description.timestamp();
+        this.creationTime = description.creationTime();
 
         // Build the graph
         this.graph = new DefaultTopologyGraph(description.vertexes(),
-                                              description.edges());
+                description.edges());
 
         this.clusterResults = Suppliers.memoize(() -> searchForClusters());
         this.clusters = Suppliers.memoize(() -> buildTopologyClusters());
@@ -101,7 +104,8 @@
 
         this.weight = new HopCountLinkWeight(graph.getVertexes().size());
         this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
-        this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
+        this.infrastructurePoints = Suppliers
+                .memoize(() -> findInfrastructurePoints());
         this.computeCost = Math.max(0, System.nanoTime() - time);
     }
 
@@ -111,6 +115,11 @@
     }
 
     @Override
+    public long creationTime() {
+        return creationTime;
+    }
+
+    @Override
     public long computeCost() {
         return computeCost;
     }
@@ -164,6 +173,7 @@
      * Returns the specified topology cluster.
      *
      * @param clusterId cluster identifier
+     *
      * @return topology cluster
      */
     TopologyCluster getCluster(ClusterId clusterId) {
@@ -174,6 +184,7 @@
      * Returns the topology cluster that contains the given device.
      *
      * @param deviceId device identifier
+     *
      * @return topology cluster
      */
     TopologyCluster getCluster(DeviceId deviceId) {
@@ -184,6 +195,7 @@
      * Returns the set of cluster devices.
      *
      * @param cluster topology cluster
+     *
      * @return cluster devices
      */
     Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
@@ -194,6 +206,7 @@
      * Returns the set of cluster links.
      *
      * @param cluster topology cluster
+     *
      * @return cluster links
      */
     Set<Link> getClusterLinks(TopologyCluster cluster) {
@@ -204,6 +217,7 @@
      * Indicates whether the given point is an infrastructure link end-point.
      *
      * @param connectPoint connection point
+     *
      * @return true if infrastructure
      */
     boolean isInfrastructure(ConnectPoint connectPoint) {
@@ -214,6 +228,7 @@
      * Indicates whether the given point is part of a broadcast set.
      *
      * @param connectPoint connection point
+     *
      * @return true if in broadcast set
      */
     boolean isBroadcastPoint(ConnectPoint connectPoint) {
@@ -225,19 +240,21 @@
         // Find the cluster to which the device belongs.
         TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId());
         if (cluster == null) {
-            throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
+            throw new IllegalArgumentException("No cluster found for device "
+                    + connectPoint.deviceId());
         }
 
         // If the broadcast set is null or empty, or if the point explicitly
         // belongs to it, return true;
         Set<ConnectPoint> points = broadcastSets.get().get(cluster.id());
-        return points == null || points.isEmpty() || points.contains(connectPoint);
+        return (points == null) || points.isEmpty() || points.contains(connectPoint);
     }
 
     /**
      * Returns the size of the cluster broadcast set.
      *
      * @param clusterId cluster identifier
+     *
      * @return size of the cluster broadcast set
      */
     int broadcastSetSize(ClusterId clusterId) {
@@ -249,7 +266,9 @@
      * destination devices.
      *
      * @param src source device
+     *
      * @param dst destination device
+     *
      * @return set of shortest paths
      */
     Set<Path> getPaths(DeviceId src, DeviceId dst) {
@@ -260,9 +279,12 @@
      * Computes on-demand the set of shortest paths between source and
      * destination devices.
      *
-     * @param src    source device
-     * @param dst    destination device
+     * @param src source device
+     *
+     * @param dst destination device
+     *
      * @param weight link weight function
+     *
      * @return set of shortest paths
      */
     Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
@@ -283,7 +305,6 @@
         return builder.build();
     }
 
-
     // Converts graph path to a network path with the same cost.
     private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
         List<Link> links = new ArrayList<>();
@@ -293,7 +314,6 @@
         return new DefaultPath(CORE_PROVIDER_ID, links, path.cost());
     }
 
-
     // Searches for SCC clusters in the network topology graph using Tarjan
     // algorithm.
     private SCCResult<TopologyVertex, TopologyEdge> searchForClusters() {
@@ -315,9 +335,10 @@
             Set<TopologyEdge> edgeSet = clusterEdges.get(i);
 
             ClusterId cid = ClusterId.clusterId(i);
-            DefaultTopologyCluster cluster =
-                    new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
-                                               findRoot(vertexSet));
+            DefaultTopologyCluster cluster = new DefaultTopologyCluster(cid,
+                                                                        vertexSet.size(),
+                                                                        edgeSet.size(),
+                                                                        findRoot(vertexSet));
             clusterBuilder.put(cid, cluster);
         }
         return clusterBuilder.build();
@@ -328,9 +349,8 @@
     private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
         TopologyVertex minVertex = null;
         for (TopologyVertex vertex : vertexSet) {
-            if (minVertex == null ||
-                    minVertex.deviceId().toString()
-                            .compareTo(minVertex.deviceId().toString()) < 0) {
+            if ((minVertex == null) || (minVertex.deviceId().toString()
+                    .compareTo(minVertex.deviceId().toString()) < 0)) {
                 minVertex = vertex;
             }
         }
@@ -349,8 +369,7 @@
     // Finds all broadcast points for the cluster. These are those connection
     // points which lie along the shortest paths between the cluster root and
     // all other devices within the cluster.
-    private void addClusterBroadcastSet(TopologyCluster cluster,
-                                        Builder<ClusterId, ConnectPoint> builder) {
+    private void addClusterBroadcastSet(TopologyCluster cluster, Builder<ClusterId, ConnectPoint> builder) {
         // Use the graph root search results to build the broadcast set.
         Result<TopologyVertex, TopologyEdge> result =
                 DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
@@ -389,9 +408,12 @@
     // Builds cluster-devices, cluster-links and device-cluster indexes.
     private ClusterIndexes buildIndexes() {
         // Prepare the index builders
-        ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
-        ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
-        ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
+        ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder =
+                ImmutableMap.builder();
+        ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder =
+                ImmutableSetMultimap.builder();
+        ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder =
+                ImmutableSetMultimap.builder();
 
         // Now scan through all the clusters
         for (TopologyCluster cluster : clusters.get().values()) {
@@ -411,8 +433,7 @@
 
         // Finalize all indexes.
         return new ClusterIndexes(clusterBuilder.build(),
-                                  devicesBuilder.build(),
-                                  linksBuilder.build());
+                devicesBuilder.build(), linksBuilder.build());
     }
 
     // Link weight for measuring link cost as hop count with indirect links
@@ -428,8 +449,9 @@
         public double weight(TopologyEdge edge) {
             // To force preference to use direct paths first, make indirect
             // links as expensive as the linear vertex traversal.
-            return edge.link().state() == ACTIVE ?
-                    (edge.link().type() == INDIRECT ? indirectLinkCost : 1) : -1;
+            return edge.link().state() ==
+                    ACTIVE ? (edge.link().type() ==
+                    INDIRECT ? indirectLinkCost : 1) : -1;
         }
     }
 
@@ -437,7 +459,8 @@
     private static class NoIndirectLinksWeight implements LinkWeight {
         @Override
         public double weight(TopologyEdge edge) {
-            return edge.link().state() == INACTIVE || edge.link().type() == INDIRECT ? -1 : 1;
+            return (edge.link().state() == INACTIVE)
+                    || (edge.link().type() == INDIRECT) ? -1 : 1;
         }
     }
 
@@ -446,9 +469,10 @@
         final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
         final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
 
-        public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
-                              ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
-                              ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
+        public ClusterIndexes(
+                ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
+                ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
+                ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
             this.clustersByDevice = clustersByDevice;
             this.devicesByCluster = devicesByCluster;
             this.linksByCluster = linksByCluster;
@@ -459,10 +483,10 @@
     public String toString() {
         return toStringHelper(this)
                 .add("time", time)
+                .add("created", creationTime)
                 .add("computeCost", computeCost)
                 .add("clusters", clusterCount())
                 .add("devices", deviceCount())
-                .add("links", linkCount())
-                .toString();
+                .add("links", linkCount()).toString();
     }
 }
diff --git a/web/api/src/test/java/org/onosproject/rest/TopologyResourceTest.java b/web/api/src/test/java/org/onosproject/rest/TopologyResourceTest.java
index 745d341..da5f969 100644
--- a/web/api/src/test/java/org/onosproject/rest/TopologyResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/TopologyResourceTest.java
@@ -61,6 +61,11 @@
         }
 
         @Override
+        public long creationTime() {
+            return 22222L;
+        }
+
+        @Override
         public long computeCost() {
             return 0;
         }