[ONOS-6756] Replicate node version information for ISSU

Change-Id: Ibd31c573990f2732b7abf8615ca914ffb77615ec
diff --git a/cli/src/main/java/org/onosproject/cli/NodesListCommand.java b/cli/src/main/java/org/onosproject/cli/NodesListCommand.java
index 1ecb3f2..9c43f6f 100644
--- a/cli/src/main/java/org/onosproject/cli/NodesListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/NodesListCommand.java
@@ -24,6 +24,7 @@
 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.util.Collections;
@@ -39,7 +40,7 @@
         description = "Lists all controller cluster nodes")
 public class NodesListCommand extends AbstractShellCommand {
 
-    private static final String FMT = "id=%s, address=%s:%s, state=%s, updated=%s %s";
+    private static final String FMT = "id=%s, address=%s:%s, state=%s, version=%s, updated=%s %s";
 
     @Override
     protected void execute() {
@@ -56,9 +57,12 @@
                 if (lastUpdated != null) {
                     timeAgo = Tools.timeAgo(lastUpdated.getMillis());
                 }
+                Version version = service.getVersion(node.id());
                 print(FMT, node.id(), node.ip(), node.tcpPort(),
-                      service.getState(node.id()), timeAgo,
-                      node.equals(self) ? "*" : "");
+                        service.getState(node.id()),
+                        version == null ? "unknown" : version,
+                        timeAgo,
+                        node.equals(self) ? "*" : "");
             }
         }
     }
@@ -70,6 +74,7 @@
         ControllerNode self = service.getLocalNode();
         for (ControllerNode node : nodes) {
             ControllerNode.State nodeState = service.getState(node.id());
+            Version nodeVersion = service.getVersion(node.id());
             ObjectNode newNode = mapper.createObjectNode()
                     .put("id", node.id().toString())
                     .put("ip", node.ip().toString())
@@ -78,6 +83,9 @@
             if (nodeState != null) {
                 newNode.put("state", nodeState.toString());
             }
+            if (nodeVersion != null) {
+                newNode.put("version", nodeVersion.toString());
+            }
             result.add(newNode);
         }
         return result;
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 aa984c0..1c4e2d7 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterService.java
@@ -18,6 +18,7 @@
 import java.util.Set;
 
 import org.joda.time.DateTime;
+import org.onosproject.core.Version;
 import org.onosproject.event.ListenerService;
 
 /**
@@ -60,6 +61,14 @@
     ControllerNode.State getState(NodeId nodeId);
 
     /**
+     * Returns the version of the given controller node.
+     *
+     * @param nodeId controller node identifier
+     * @return controller version
+     */
+    Version getVersion(NodeId nodeId);
+
+    /**
      * 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/cluster/ClusterStore.java b/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
index e3660f3..a181c4f 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
@@ -17,6 +17,7 @@
 
 import org.joda.time.DateTime;
 import org.onlab.packet.IpAddress;
+import org.onosproject.core.Version;
 import org.onosproject.store.Store;
 
 import java.util.Set;
@@ -57,6 +58,14 @@
     ControllerNode.State getState(NodeId nodeId);
 
     /**
+     * Returns the version of the specified controller node.
+     *
+     * @param nodeId controller instance identifier
+     * @return controller version
+     */
+    Version getVersion(NodeId nodeId);
+
+    /**
      * Marks the current node as fully started.
      *
      * @param started true indicates all components have been started
diff --git a/core/api/src/main/java/org/onosproject/core/VersionService.java b/core/api/src/main/java/org/onosproject/core/VersionService.java
new file mode 100644
index 0000000..409ffbe
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/core/VersionService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017-present 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.core;
+
+/**
+ * Node version service.
+ */
+public interface VersionService {
+
+    /**
+     * Returns the product version.
+     *
+     * @return product version
+     */
+    Version version();
+
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java b/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java
index 6f7917d..3c8cd36 100644
--- a/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/cluster/ClusterServiceAdapter.java
@@ -21,6 +21,7 @@
 import org.onlab.packet.IpAddress;
 
 import com.google.common.collect.ImmutableSet;
+import org.onosproject.core.Version;
 
 /**
  * Test adapter for the cluster service.
@@ -50,6 +51,11 @@
     }
 
     @Override
+    public Version getVersion(NodeId nodeId) {
+        return null;
+    }
+
+    @Override
     public DateTime getLastUpdated(NodeId nodeId) {
         return null;
     }
diff --git a/core/api/src/test/java/org/onosproject/core/VersionServiceAdapter.java b/core/api/src/test/java/org/onosproject/core/VersionServiceAdapter.java
new file mode 100644
index 0000000..d0031c0
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/core/VersionServiceAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017-present 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.core;
+
+/**
+ * Version service adapter.
+ */
+public class VersionServiceAdapter implements VersionService {
+    @Override
+    public Version version() {
+        return null;
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java
index ea65b54..d9c72d8 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleClusterStore.java
@@ -31,6 +31,8 @@
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.DefaultControllerNode;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
 import org.onosproject.event.EventDeliveryService;
 import org.onosproject.event.ListenerRegistry;
 import org.onosproject.net.intent.WorkPartitionEvent;
@@ -67,6 +69,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected EventDeliveryService eventDispatcher;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VersionService versionService;
+
     private ListenerRegistry<WorkPartitionEvent, WorkPartitionEventListener> listenerRegistry;
     private boolean started = false;
 
@@ -108,6 +113,11 @@
     }
 
     @Override
+    public Version getVersion(NodeId nodeId) {
+        return instance.id().equals(nodeId) ? versionService.version() : null;
+    }
+
+    @Override
     public void markFullyStarted(boolean started) {
         this.started = started;
     }
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
index ad4386e..2a8caf0 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleMastershipStore.java
@@ -45,6 +45,8 @@
 import org.onosproject.cluster.DefaultControllerNode;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
 import org.onosproject.mastership.MastershipEvent;
 import org.onosproject.mastership.MastershipStore;
 import org.onosproject.mastership.MastershipStoreDelegate;
@@ -75,6 +77,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VersionService versionService;
+
     //devices mapped to their masters, to emulate multiple nodes
     protected final Map<DeviceId, NodeId> masterMap = new HashMap<>();
     //emulate backups with pile of nodes
@@ -122,6 +127,14 @@
                 }
 
                 @Override
+                public Version getVersion(NodeId nodeId) {
+                    if (instance.id().equals(nodeId)) {
+                        return versionService.version();
+                    }
+                    return null;
+                }
+
+                @Override
                 public DateTime getLastUpdated(NodeId nodeId) {
                     return creationTime;
                 }
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java b/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
index a2b9000..b7c9045 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
@@ -44,6 +44,7 @@
 import org.onosproject.cluster.NodeId;
 import org.onosproject.cluster.Partition;
 import org.onosproject.cluster.PartitionId;
+import org.onosproject.core.Version;
 import org.onosproject.event.AbstractListenerManager;
 import org.slf4j.Logger;
 
@@ -135,6 +136,13 @@
     }
 
     @Override
+    public Version getVersion(NodeId nodeId) {
+        checkPermission(CLUSTER_READ);
+        checkNotNull(nodeId, INSTANCE_ID_NULL);
+        return store.getVersion(nodeId);
+    }
+
+    @Override
     public void markFullyStarted(boolean started) {
         store.markFullyStarted(started);
     }
diff --git a/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java b/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java
index cda738f..4ff6eb0 100644
--- a/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java
+++ b/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java
@@ -35,18 +35,13 @@
 import org.onosproject.core.IdBlockStore;
 import org.onosproject.core.IdGenerator;
 import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
 import org.onosproject.event.EventDeliveryService;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Dictionary;
-import java.util.List;
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -54,7 +49,6 @@
 import static org.onosproject.security.AppPermission.Type.APP_READ;
 import static org.onosproject.security.AppPermission.Type.APP_WRITE;
 
-
 /**
  * Core service implementation.
  */
@@ -64,8 +58,8 @@
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
-    private static final File VERSION_FILE = new File("../VERSION");
-    private static Version version = Version.version("1.11.0-SNAPSHOT");
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VersionService versionService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ApplicationIdStore applicationIdStore;
@@ -105,16 +99,6 @@
     protected void activate() {
         registerApplication(CORE_APP_NAME);
         cfgService.registerProperties(getClass());
-        try {
-            Path path = Paths.get(VERSION_FILE.getPath());
-            List<String> versionLines = Files.readAllLines(path);
-            if (versionLines != null && !versionLines.isEmpty()) {
-                version = Version.version(versionLines.get(0));
-            }
-        } catch (IOException e) {
-            // version file not found, using default
-            log.trace("Version file not found", e);
-        }
     }
 
     @Deactivate
@@ -127,7 +111,7 @@
     @Override
     public Version version() {
         checkPermission(APP_READ);
-        return version;
+        return versionService.version();
     }
 
     @Override
@@ -148,7 +132,6 @@
         return applicationIdStore.getAppId(name);
     }
 
-
     @Override
     public ApplicationId registerApplication(String name) {
         checkPermission(APP_WRITE);
@@ -171,7 +154,6 @@
         return new BlockAllocatorBasedIdGenerator(allocator);
     }
 
-
     @Modified
     protected void modified(ComponentContext context) {
         Dictionary<?, ?> properties = context.getProperties();
diff --git a/core/net/src/main/java/org/onosproject/core/impl/VersionManager.java b/core/net/src/main/java/org/onosproject/core/impl/VersionManager.java
new file mode 100644
index 0000000..c683205
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/core/impl/VersionManager.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017-present 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.core.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Node version service implementation.
+ */
+@Component(immediate = true)
+@Service
+public class VersionManager implements VersionService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final File VERSION_FILE = new File("../VERSION");
+    private static Version version = Version.version("1.11.0-SNAPSHOT");
+
+    @Activate
+    protected void activate() {
+        try {
+            Path path = Paths.get(VERSION_FILE.getPath());
+            List<String> versionLines = Files.readAllLines(path);
+            if (versionLines != null && !versionLines.isEmpty()) {
+                version = Version.version(versionLines.get(0));
+            }
+        } catch (IOException e) {
+            // version file not found, using default
+            log.trace("Version file not found", e);
+        }
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+}
\ No newline at end of file
diff --git a/core/net/src/test/java/org/onosproject/core/impl/VersionManagerTest.java b/core/net/src/test/java/org/onosproject/core/impl/VersionManagerTest.java
new file mode 100644
index 0000000..aa87d90
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/core/impl/VersionManagerTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017-present 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.core.impl;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Version manager test.
+ */
+public class VersionManagerTest {
+    @Test
+    public void testVersionManager() throws Exception {
+        VersionManager versionManager = new VersionManager();
+        assertNotNull(versionManager.version());
+    }
+}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
index d727ebe..4374a43 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.store.cluster.impl;
 
-import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 
@@ -41,6 +40,8 @@
 import org.onosproject.cluster.ControllerNode.State;
 import org.onosproject.cluster.DefaultControllerNode;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.cluster.messaging.Endpoint;
 import org.onosproject.store.cluster.messaging.MessagingService;
@@ -59,6 +60,7 @@
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Strings.isNullOrEmpty;
@@ -93,7 +95,7 @@
     private int phiFailureThreshold = DEFAULT_PHI_FAILURE_THRESHOLD;
 
     private static final StoreSerializer SERIALIZER = StoreSerializer.using(
-                  KryoNamespace.newBuilder()
+            KryoNamespace.newBuilder()
                     .register(KryoNamespaces.API)
                     .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
                     .register(HeartbeatMessage.class)
@@ -103,7 +105,8 @@
 
     private final Map<NodeId, ControllerNode> allNodes = Maps.newConcurrentMap();
     private final Map<NodeId, State> nodeStates = Maps.newConcurrentMap();
-    private final Map<NodeId, DateTime> nodeStateLastUpdatedTimes = Maps.newConcurrentMap();
+    private final Map<NodeId, Version> nodeVersions = Maps.newConcurrentMap();
+    private final Map<NodeId, DateTime> nodeLastUpdatedTimes = Maps.newConcurrentMap();
 
     private ScheduledExecutorService heartBeatSender = Executors.newSingleThreadScheduledExecutor(
             groupedThreads("onos/cluster/membership", "heartbeat-sender", log));
@@ -113,6 +116,10 @@
     private PhiAccrualFailureDetector failureDetector;
 
     private ControllerNode localNode;
+    private Version localVersion;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VersionService versionService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterMetadataService clusterMetadataService;
@@ -122,9 +129,9 @@
 
     // This must be optional to avoid a cyclic dependency
     @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY,
-               bind = "bindComponentConfigService",
-               unbind = "unbindComponentConfigService",
-               policy = ReferencePolicy.DYNAMIC)
+            bind = "bindComponentConfigService",
+            unbind = "unbindComponentConfigService",
+            policy = ReferencePolicy.DYNAMIC)
     protected ComponentConfigService cfgService;
 
     /**
@@ -155,14 +162,16 @@
     @Activate
     public void activate() {
         localNode = clusterMetadataService.getLocalNode();
+        localVersion = versionService.version();
+        nodeVersions.put(localNode.id(), localVersion);
 
         messagingService.registerHandler(HEARTBEAT_MESSAGE,
-                                         new HeartbeatMessageHandler(), heartBeatMessageHandler);
+                new HeartbeatMessageHandler(), heartBeatMessageHandler);
 
         failureDetector = new PhiAccrualFailureDetector();
 
         heartBeatSender.scheduleWithFixedDelay(this::heartbeat, 0,
-                                               heartbeatInterval, TimeUnit.MILLISECONDS);
+                heartbeatInterval, TimeUnit.MILLISECONDS);
 
         log.info("Started");
     }
@@ -216,12 +225,18 @@
     @Override
     public State getState(NodeId nodeId) {
         checkNotNull(nodeId, INSTANCE_ID_NULL);
-        return MoreObjects.firstNonNull(nodeStates.get(nodeId), State.INACTIVE);
+        return firstNonNull(nodeStates.get(nodeId), State.INACTIVE);
+    }
+
+    @Override
+    public Version getVersion(NodeId nodeId) {
+        checkNotNull(nodeId, INSTANCE_ID_NULL);
+        return nodeVersions.get(nodeId);
     }
 
     @Override
     public void markFullyStarted(boolean started) {
-        updateState(localNode.id(), started ? State.READY : State.ACTIVE);
+        updateNode(localNode.id(), started ? State.READY : State.ACTIVE, null);
     }
 
     @Override
@@ -238,22 +253,27 @@
         ControllerNode node = allNodes.remove(nodeId);
         if (node != null) {
             nodeStates.remove(nodeId);
+            nodeVersions.remove(nodeId);
             notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_REMOVED, node));
         }
     }
 
     private void addNode(ControllerNode node) {
         allNodes.put(node.id(), node);
-        updateState(node.id(), node.equals(localNode) ? State.ACTIVE : State.INACTIVE);
+        updateNode(node.id(), node.equals(localNode) ? State.ACTIVE : State.INACTIVE, null);
         notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_ADDED, node));
     }
 
-    private void updateState(NodeId nodeId, State newState) {
+    private void updateNode(NodeId nodeId, State newState, Version newVersion) {
         State currentState = nodeStates.get(nodeId);
-        if (!Objects.equals(currentState, newState)) {
+        Version currentVersion = nodeVersions.get(nodeId);
+        if (!Objects.equals(currentState, newState) || !Objects.equals(currentVersion, newVersion)) {
             nodeStates.put(nodeId, newState);
-            nodeStateLastUpdatedTimes.put(nodeId, DateTime.now());
-            notifyStateChange(nodeId, currentState, newState);
+            if (newVersion != null) {
+                nodeVersions.put(nodeId, newVersion);
+            }
+            nodeLastUpdatedTimes.put(nodeId, DateTime.now());
+            notifyChange(nodeId, currentState, newState, currentVersion, newVersion);
         }
     }
 
@@ -264,18 +284,18 @@
                     .filter(node -> !(node.id().equals(localNode.id())))
                     .collect(Collectors.toSet());
             State state = nodeStates.get(localNode.id());
-            byte[] hbMessagePayload = SERIALIZER.encode(new HeartbeatMessage(localNode, state));
+            byte[] hbMessagePayload = SERIALIZER.encode(new HeartbeatMessage(localNode, state, localVersion));
             peers.forEach((node) -> {
                 heartbeatToPeer(hbMessagePayload, node);
                 State currentState = nodeStates.get(node.id());
                 double phi = failureDetector.phi(node.id());
                 if (phi >= phiFailureThreshold) {
                     if (currentState.isActive()) {
-                        updateState(node.id(), State.INACTIVE);
+                        updateNode(node.id(), State.INACTIVE, null);
                     }
                 } else {
                     if (currentState == State.INACTIVE) {
-                        updateState(node.id(), State.ACTIVE);
+                        updateNode(node.id(), State.ACTIVE, null);
                     }
                 }
             });
@@ -284,8 +304,8 @@
         }
     }
 
-    private void notifyStateChange(NodeId nodeId, State oldState, State newState) {
-        if (oldState != newState) {
+    private void notifyChange(NodeId nodeId, State oldState, State newState, Version oldVersion, Version newVersion) {
+        if (oldState != newState || !Objects.equals(oldVersion, newVersion)) {
             ControllerNode node = allNodes.get(nodeId);
             // Either this node or that node is no longer part of the same cluster
             if (node == null) {
@@ -314,7 +334,7 @@
             HeartbeatMessage hb = SERIALIZER.decode(message);
             if (clusterMetadataService.getClusterMetadata().getNodes().contains(hb.source())) {
                 failureDetector.report(hb.source().id());
-                updateState(hb.source().id(), hb.state);
+                updateNode(hb.source().id(), hb.state, hb.version);
             }
         }
     }
@@ -322,10 +342,12 @@
     private static class HeartbeatMessage {
         private ControllerNode source;
         private State state;
+        private Version version;
 
-        public HeartbeatMessage(ControllerNode source, State state) {
+        public HeartbeatMessage(ControllerNode source, State state, Version version) {
             this.source = source;
             this.state = state != null ? state : State.ACTIVE;
+            this.version = version;
         }
 
         public ControllerNode source() {
@@ -335,7 +357,7 @@
 
     @Override
     public DateTime getLastUpdated(NodeId nodeId) {
-        return nodeStateLastUpdatedTimes.get(nodeId);
+        return nodeLastUpdatedTimes.get(nodeId);
     }
 
     /**
@@ -353,7 +375,7 @@
                             DEFAULT_HEARTBEAT_INTERVAL);
                 } else {
                     int newHeartbeatInterval = isNullOrEmpty(s) ? DEFAULT_HEARTBEAT_INTERVAL
-                                                                : Integer.parseInt(s.trim());
+                            : Integer.parseInt(s.trim());
                     if (newHeartbeatInterval > 0 && heartbeatInterval != newHeartbeatInterval) {
                         heartbeatInterval = newHeartbeatInterval;
                         restartHeartbeatSender();
@@ -370,7 +392,7 @@
                             DEFAULT_PHI_FAILURE_THRESHOLD);
                 } else {
                     int newPhiFailureThreshold = isNullOrEmpty(s) ? DEFAULT_HEARTBEAT_INTERVAL
-                                                                  : Integer.parseInt(s.trim());
+                            : Integer.parseInt(s.trim());
                     setPhiFailureThreshold(newPhiFailureThreshold);
                     log.info("Configured. Phi failure threshold is configured to {}",
                             phiFailureThreshold);
@@ -414,10 +436,10 @@
             heartBeatSender = Executors.newSingleThreadScheduledExecutor(
                     groupedThreads("onos/cluster/membership", "heartbeat-sender-%d", log));
             heartBeatSender.scheduleWithFixedDelay(this::heartbeat, 0,
-                                                   heartbeatInterval, TimeUnit.MILLISECONDS);
+                    heartbeatInterval, TimeUnit.MILLISECONDS);
             prevSender.shutdown();
         } catch (Exception e) {
             log.warn(e.getMessage());
         }
     }
-}
+}
\ No newline at end of file
diff --git a/core/store/dist/src/test/java/org/onosproject/store/cluster/impl/DistributedClusterStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/cluster/impl/DistributedClusterStoreTest.java
index 512d162..7c48791 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/cluster/impl/DistributedClusterStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/cluster/impl/DistributedClusterStoreTest.java
@@ -27,6 +27,8 @@
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.DefaultControllerNode;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionServiceAdapter;
 import org.onosproject.store.cluster.messaging.impl.NettyMessagingManager;
 
 import java.util.Set;
@@ -55,7 +57,6 @@
     private static final int PORT2 = 2;
     private  static Set<ControllerNode> nodes;
 
-
     private TestDelegate delegate = new TestDelegate();
     private class TestDelegate implements ClusterStoreDelegate {
     private ClusterEvent event;
@@ -65,8 +66,6 @@
         }
     }
 
-
-
     @Before
     public void setUp() throws Exception {
         distributedClusterStore = new DistributedClusterStore();
@@ -78,6 +77,12 @@
         };
         distributedClusterStore.messagingService = new NettyMessagingManager();
         distributedClusterStore.cfgService = new ComponentConfigAdapter();
+        distributedClusterStore.versionService = new VersionServiceAdapter() {
+            @Override
+            public Version version() {
+                return Version.version("1.1.1");
+            }
+        };
         distributedClusterStore.activate();
         clusterStore = distributedClusterStore;
     }
@@ -93,11 +98,11 @@
         assertThat(clusterStore.getNode((nodeId)), is(nullValue()));
         assertFalse(clusterStore.hasDelegate());
         assertThat(clusterStore.getState(nodeId), is(ControllerNode.State.INACTIVE));
+        assertThat(clusterStore.getVersion(nodeId), is(nullValue()));
     }
 
     @Test
     public void addNodes() {
-
         clusterStore.setDelegate(delegate);
         assertThat(clusterStore.hasDelegate(), is(true));
         clusterStore.addNode(NID1, IP1, PORT1);
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualMastershipStore.java b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualMastershipStore.java
index 47b9c6f..c390513 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualMastershipStore.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/virtual/impl/SimpleVirtualMastershipStore.java
@@ -32,6 +32,8 @@
 import org.onosproject.cluster.DefaultControllerNode;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.cluster.RoleInfo;
+import org.onosproject.core.Version;
+import org.onosproject.core.VersionService;
 import org.onosproject.incubator.net.virtual.NetworkId;
 import org.onosproject.incubator.net.virtual.VirtualNetworkMastershipStore;
 import org.onosproject.mastership.MastershipEvent;
@@ -74,6 +76,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VersionService versionService;
+
     //devices mapped to their masters, to emulate multiple nodes
     protected final Map<NetworkId, Map<DeviceId, NodeId>> masterMapByNetwork =
             new HashMap<>();
@@ -479,6 +484,14 @@
             }
 
             @Override
+            public Version getVersion(NodeId nodeId) {
+                if (instance.id().equals(nodeId)) {
+                    return versionService.version();
+                }
+                return null;
+            }
+
+            @Override
             public DateTime getLastUpdated(NodeId nodeId) {
                 return creationTime;
             }
diff --git a/tools/build/onos-change-version b/tools/build/onos-change-version
index f0de5f6..a985e9a 100755
--- a/tools/build/onos-change-version
+++ b/tools/build/onos-change-version
@@ -33,9 +33,9 @@
 sed -i "" -E "s/ -Dversion=.*\"/ -Dversion=$NEW_VERSION\"/" $ONOS_ROOT/tools/test/bin/onos-archetypes-test
 sed -i "" -E "s/ONOS_POM_VERSION=.*\"/ONOS_POM_VERSION=\"$NEW_VERSION\"/" $ONOS_ROOT/tools/build/envDefaults
 
-# Augment fallback version in CoreManager
+# Augment fallback version in VersionManager
 sed -i "" -E "s/Version\.version\(\"[^\"]*\"\)/Version.version(\"$NEW_VERSION\")/" \
-   $ONOS_ROOT/core/net/src/main/java/org/onosproject/core/impl/CoreManager.java
+   $ONOS_ROOT/core/net/src/main/java/org/onosproject/core/impl/VersionManager.java
 
 # Augment the version in archetypes tree.
 mvn -f tools/package/archetypes/pom.xml versions:set -DnewVersion=$NEW_VERSION versions:commit