[Goldeneye] CORD-568 Ensure location transparency of node init operation

- Added MapListener for cordvtn node store and made the init process to be
  triggered by map event, so that the leader can do its job regardless of
  the location where node init CLI command happens
- Fixed equals and hashCode override to use all node attributes except for
  the node init state
- Adjusted some log levels

Change-Id: I45688afa60de3516d91132e8a6c49cf90c4dcae4
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
index 770b6a1..f20a085 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -718,7 +718,7 @@
                 .stream()
                 .forEach(entry -> {
                     arpProxy.addGateway(entry.getKey(), entry.getValue());
-                    log.debug("Added public gateway IP {}, MAC {}",
+                    log.info("Added public gateway IP {}, MAC {}",
                               entry.getKey().toString(), entry.getValue().toString());
                 });
         // TODO notice gateway MAC change to VMs holds this gateway IP
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnConfig.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnConfig.java
index 1b29a5e..2e3f3d2 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnConfig.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnConfig.java
@@ -97,7 +97,6 @@
                         getConfig(jsonNode, DATA_PLANE_INTF),
                         CordVtnNodeState.noState());
 
-                log.info("Successfully read {} from the config", hostname);
                 nodes.add(newNode);
             } catch (IllegalArgumentException | NullPointerException e) {
                 log.error("{}", e.toString());
@@ -121,7 +120,6 @@
             log.error("{} is not configured", path);
             return null;
         } else {
-            log.debug("{} : {}", path, jsonNode.asText());
             return jsonNode.asText();
         }
     }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java
index 303c927..0dcf11b 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java
@@ -181,11 +181,16 @@
             return true;
         }
 
-        // hostname here is a network hostname and it is intended to be
-        // unique throughout the service.
         if (obj instanceof CordVtnNode) {
             CordVtnNode that = (CordVtnNode) obj;
-            if (Objects.equals(hostname, that.hostname)) {
+            if (Objects.equals(hostname, that.hostname) &&
+                    Objects.equals(hostMgmtIp, that.hostMgmtIp) &&
+                    Objects.equals(localMgmtIp, that.localMgmtIp) &&
+                    Objects.equals(dpIp, that.dpIp) &&
+                    Objects.equals(ovsdbPort, that.ovsdbPort) &&
+                    Objects.equals(sshInfo, that.sshInfo) &&
+                    Objects.equals(bridgeId, that.bridgeId) &&
+                    Objects.equals(dpIntf, that.dpIntf)) {
                 return true;
             }
         }
@@ -194,7 +199,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(hostname);
+        return Objects.hash(hostname, hostMgmtIp, localMgmtIp, dpIp,
+                            ovsdbPort, sshInfo, bridgeId, dpIntf);
     }
 
     @Override
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeManager.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeManager.java
index 8314f96..52d4668 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeManager.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeManager.java
@@ -62,6 +62,8 @@
 import org.onosproject.ovsdb.controller.OvsdbNodeId;
 import org.onosproject.store.serializers.KryoNamespaces;
 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.Versioned;
@@ -164,6 +166,7 @@
 
     private final NetworkConfigListener configListener = new InternalConfigListener();
     private final DeviceListener deviceListener = new InternalDeviceListener();
+    private final MapEventListener<String, CordVtnNode> nodeStoreListener = new InternalMapListener();
 
     private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
     private final BridgeHandler bridgeHandler = new BridgeHandler();
@@ -236,6 +239,7 @@
                                                  configRegistry,
                                                  DEFAULT_TUNNEL);
 
+        nodeStore.addListener(nodeStoreListener);
         deviceService.addListener(deviceListener);
         configService.addListener(configListener);
     }
@@ -245,22 +249,21 @@
         configService.removeListener(configListener);
         deviceService.removeListener(deviceListener);
 
-        eventExecutor.shutdown();
+        nodeStore.removeListener(nodeStoreListener);
         nodeStore.clear();
+
         leadershipService.withdraw(appId.name());
+        eventExecutor.shutdown();
     }
 
     /**
-     * Adds a new node to the service.
+     * Adds or updates a new node to the service.
      *
      * @param node cordvtn node
      */
-    public void addNode(CordVtnNode node) {
+    public void addOrUpdateNode(CordVtnNode node) {
         checkNotNull(node);
-
-        // allow update node attributes
         nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, getNodeState(node)));
-        initNode(node);
     }
 
     /**
@@ -283,23 +286,12 @@
      *
      * @param node cordvtn node
      */
-    public void initNode(CordVtnNode node) {
+    private void initNode(CordVtnNode node) {
         checkNotNull(node);
 
-        if (!nodeStore.containsKey(node.hostname())) {
-            log.warn("Node {} does not exist, add node first", node.hostname());
-            return;
-        }
+        NodeState state = (NodeState) node.state();
+        log.debug("Processing node: {} state: {}", node.hostname(), state);
 
-        NodeId leaderNodeId = leadershipService.getLeader(appId.name());
-        log.debug("Node init requested, local: {} leader: {}", localNodeId, leaderNodeId);
-        if (!Objects.equals(localNodeId, leaderNodeId)) {
-            // only the leader performs node init
-            return;
-        }
-
-        NodeState state = getNodeState(node);
-        log.debug("Init node: {} state: {}", node.hostname(), state.toString());
         state.process(this, node);
     }
 
@@ -432,9 +424,8 @@
     private void setNodeState(CordVtnNode node, NodeState newState) {
         checkNotNull(node);
 
-        log.debug("Changed {} state: {}", node.hostname(), newState.toString());
+        log.debug("Changed {} state: {}", node.hostname(), newState);
         nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
-        newState.process(this, node);
     }
 
     /**
@@ -812,7 +803,7 @@
                 return;
             }
 
-            log.debug("Port {} is added to {}", portName, node.hostname());
+            log.info("Port {} is added to {}", portName, node.hostname());
 
             if (portName.startsWith(VPORT_PREFIX)) {
                 if (isNodeStateComplete(node)) {
@@ -840,7 +831,7 @@
                 return;
             }
 
-            log.debug("Port {} is removed from {}", portName, node.hostname());
+            log.info("Port {} is removed from {}", portName, node.hostname());
 
             if (portName.startsWith(VPORT_PREFIX)) {
                 if (isNodeStateComplete(node)) {
@@ -861,7 +852,7 @@
 
             NodeId leaderNodeId = leadershipService.getLeader(appId.name());
             if (!Objects.equals(localNodeId, leaderNodeId)) {
-                // only the leader processes events
+                // do not allow to proceed without leadership
                 return;
             }
 
@@ -871,19 +862,19 @@
 
             switch (event.type()) {
                 case PORT_ADDED:
-                    eventExecutor.submit(() -> bridgeHandler.portAdded(event.port()));
+                    eventExecutor.execute(() -> bridgeHandler.portAdded(event.port()));
                     break;
                 case PORT_UPDATED:
                     if (!event.port().isEnabled()) {
-                        eventExecutor.submit(() -> bridgeHandler.portRemoved(event.port()));
+                        eventExecutor.execute(() -> bridgeHandler.portRemoved(event.port()));
                     }
                     break;
                 case DEVICE_ADDED:
                 case DEVICE_AVAILABILITY_CHANGED:
                     if (deviceService.isAvailable(device.id())) {
-                        eventExecutor.submit(() -> handler.connected(device));
+                        eventExecutor.execute(() -> handler.connected(device));
                     } else {
-                        eventExecutor.submit(() -> handler.disconnected(device));
+                        eventExecutor.execute(() -> handler.disconnected(device));
                     }
                     break;
                 default:
@@ -902,14 +893,19 @@
             return;
         }
 
-        config.cordVtnNodes().forEach(this::addNode);
-        // TODO remove nodes if needed
+        config.cordVtnNodes().forEach(this::addOrUpdateNode);
     }
 
     private class InternalConfigListener implements NetworkConfigListener {
 
         @Override
         public void event(NetworkConfigEvent event) {
+            NodeId leaderNodeId = leadershipService.getLeader(appId.name());
+            if (!Objects.equals(localNodeId, leaderNodeId)) {
+                // do not allow to proceed without leadership
+                return;
+            }
+
             if (!event.configClass().equals(CordVtnConfig.class)) {
                 return;
             }
@@ -924,4 +920,46 @@
             }
         }
     }
+
+    private class InternalMapListener implements MapEventListener<String, CordVtnNode> {
+
+        @Override
+        public void event(MapEvent<String, CordVtnNode> event) {
+            NodeId leaderNodeId = leadershipService.getLeader(appId.name());
+            if (!Objects.equals(localNodeId, leaderNodeId)) {
+                // do not allow to proceed without leadership
+                return;
+            }
+
+            CordVtnNode oldNode;
+            CordVtnNode newNode;
+
+            switch (event.type()) {
+                case UPDATE:
+                    oldNode = event.oldValue().value();
+                    newNode = event.newValue().value();
+
+                    if (!newNode.equals(oldNode)) {
+                        log.info("{} has been updated", newNode.hostname());
+                        log.debug("New node: {}", newNode);
+                    }
+                    // perform init procedure based on current state on any updates,
+                    // insert, or even if the node is the same for robustness since
+                    // it's no harm to run the init procedure multiple times
+                    eventExecutor.execute(() -> initNode(newNode));
+                    break;
+                case INSERT:
+                    newNode = event.newValue().value();
+                    log.info("Added {}", newNode.hostname());
+                    eventExecutor.execute(() -> initNode(newNode));
+                    break;
+                case REMOVE:
+                    oldNode = event.oldValue().value();
+                    log.info("{} is removed", oldNode.hostname());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
 }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java
index ab8b389..9a44dbd 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java
@@ -51,7 +51,7 @@
                 continue;
             }
 
-            nodeManager.initNode(node);
+            nodeManager.addOrUpdateNode(node);
         }
     }
 }