Cherry pick from Change-Id: I4f544b8ccf4f09db67962e4603be5a366ab0c283

Change-Id: I3dddab925dd3601ecf868e22ab25d462c56fb828
diff --git a/core/store/dist/BUCK b/core/store/dist/BUCK
index 0e4731e..ec644c9 100644
--- a/core/store/dist/BUCK
+++ b/core/store/dist/BUCK
@@ -17,6 +17,7 @@
     '//lib:TEST',
     '//core/api:onos-api-tests',
     '//core/common:onos-core-common-tests',
+    '//utils/osgi:onlab-osgi-tests',
 ]
 
 osgi_jar_with_tests (
diff --git a/core/store/dist/pom.xml b/core/store/dist/pom.xml
index 5ae168f..aa948da 100644
--- a/core/store/dist/pom.xml
+++ b/core/store/dist/pom.xml
@@ -122,6 +122,13 @@
             <artifactId>netty-resolver</artifactId>
             <version>${netty4.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java b/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
index 356df9c..7f9be04 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
@@ -21,6 +21,8 @@
 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.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
@@ -28,6 +30,7 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultAnnotations;
@@ -42,15 +45,20 @@
 import org.onosproject.net.host.HostStoreDelegate;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.store.AbstractStore;
+import org.onosproject.store.Timestamp;
 import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
 import org.onosproject.store.service.ConsistentMap;
 import org.onosproject.store.service.MapEvent;
 import org.onosproject.store.service.MapEventListener;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
 import java.util.Collection;
+import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
@@ -58,6 +66,8 @@
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static org.onosproject.net.DefaultAnnotations.merge;
@@ -75,6 +85,15 @@
 
     private final Logger log = getLogger(getClass());
 
+    private static final boolean DEFAULT_RECORD_TIMESTAMP_ENABLED = false;
+
+    @Property(name = "recordHostTimestamp", boolValue = DEFAULT_RECORD_TIMESTAMP_ENABLED,
+            label = "Indicates whether recoding of host timestamp is enabled or not")
+    private volatile boolean recordHostTimestamp = DEFAULT_RECORD_TIMESTAMP_ENABLED;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
 
@@ -84,8 +103,11 @@
     private MapEventListener<HostId, DefaultHost> hostLocationTracker =
             new HostLocationTracker();
 
+    private EventuallyConsistentMap<HostId, Timestamp> hostsTimestamp;
+
     @Activate
-    public void activate() {
+    public void activate(ComponentContext context) {
+        configService.registerProperties(getClass());
         KryoNamespace.Builder hostSerializer = KryoNamespace.newBuilder()
                 .register(KryoNamespaces.API);
 
@@ -97,19 +119,45 @@
 
         hosts = hostsConsistentMap.asJavaMap();
 
+        hostsTimestamp = storageService.<HostId, Timestamp>eventuallyConsistentMapBuilder()
+                .withName("onos-hosts-timestamp")
+                .withTimestampProvider((k, v) -> new WallClockTimestamp())
+                .withSerializer(hostSerializer)
+                .build();
 
         hostsConsistentMap.addListener(hostLocationTracker);
-
+        modified(context);
         log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
+        configService.unregisterProperties(getClass(), false);
         hostsConsistentMap.removeListener(hostLocationTracker);
 
         log.info("Stopped");
     }
 
+    @SuppressWarnings("rawtypes")
+    @Modified
+    public void modified(ComponentContext context) {
+        if (context == null) {
+            recordHostTimestamp = DEFAULT_RECORD_TIMESTAMP_ENABLED;
+            log.info("Default config");
+            return;
+        }
+
+        Dictionary properties = context.getProperties();
+        String s = get(properties, "recordHostTimestamp");
+        boolean newRecordTimestamp = isNullOrEmpty(s) ? recordHostTimestamp : Boolean.parseBoolean(s.trim());
+
+        if (newRecordTimestamp != recordHostTimestamp) {
+            recordHostTimestamp = newRecordTimestamp;
+        }
+    }
+
+
+
     private boolean shouldUpdate(DefaultHost existingHost,
                                  ProviderId providerId,
                                  HostId hostId,
@@ -152,6 +200,10 @@
                                         HostId hostId,
                                         HostDescription hostDescription,
                                         boolean replaceIPs) {
+        if (recordHostTimestamp && !hostDescription.ipAddress().isEmpty()) {
+            hostsTimestamp.put(hostId, new WallClockTimestamp());
+        }
+
         hostsConsistentMap.computeIf(hostId,
                        existingHost -> shouldUpdate(existingHost, providerId, hostId,
                                                     hostDescription, replaceIPs),
@@ -274,6 +326,11 @@
         return ImmutableSet.copyOf(filtered);
     }
 
+    @Override
+    public Timestamp getHostLastseenTime(HostId hostId) {
+        return hostsTimestamp.get(hostId);
+    }
+
     private Set<Host> filter(Collection<DefaultHost> collection, Predicate<DefaultHost> predicate) {
         return collection.stream().filter(predicate).collect(Collectors.toSet());
     }
diff --git a/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java
index 9f7338d..9ca2c98 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java
@@ -18,17 +18,22 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.osgi.ComponentContextAdapter;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.cfg.ComponentConfigAdapter;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.host.DefaultHostDescription;
 import org.onosproject.net.host.HostDescription;
 import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.Timestamp;
 import org.onosproject.store.service.TestStorageService;
 
+import java.util.Dictionary;
 import java.util.HashSet;
+import java.util.Hashtable;
 import java.util.Set;
 
 import static junit.framework.TestCase.assertTrue;
@@ -48,12 +53,22 @@
 
     private static final ProviderId PID = new ProviderId("of", "foo");
 
+    public static final ComponentContextAdapter RECORD_HOST_TIMESTAMP = new ComponentContextAdapter() {
+        @Override
+        public Dictionary getProperties() {
+            Hashtable<String, String> props = new Hashtable<String, String>();
+            props.put("recordHostTimestamp", "true");
+            return props;
+        }
+    };
+
     @Before
     public void setUp() {
         ecXHostStore = new DistributedHostStore();
 
         ecXHostStore.storageService = new TestStorageService();
-        ecXHostStore.activate();
+        ecXHostStore.configService = new ComponentConfigAdapter();
+        ecXHostStore.activate(RECORD_HOST_TIMESTAMP);
     }
 
     @After
@@ -82,4 +97,21 @@
         assertTrue(host.ipAddresses().contains(IP2));
     }
 
+    @Test
+    public void testRecordTimestamp() {
+        Set<IpAddress> ips = new HashSet<>();
+        ips.add(IP1);
+        ips.add(IP2);
+
+        HostDescription description = new DefaultHostDescription(HOSTID.mac(),
+                HOSTID.vlanId(),
+                HostLocation.NONE,
+                ips);
+        ecXHostStore.createOrUpdateHost(PID, HOSTID, description, false);
+        Timestamp timestamp = ecXHostStore.getHostLastseenTime(HOSTID);
+
+        assertFalse(timestamp == null);
+        assertTrue(timestamp != null);
+    }
+
 }