CORD-562 Don't allow nodes with duplicate hostname

And update existing node if newly added node has the same hostname with
existing node.

Change-Id: Ifebbf4129df4f742e16b9a25be619dd90e0745ff
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 a44ec21..1b29a5e 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnConfig.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnConfig.java
@@ -60,7 +60,7 @@
     /**
      * Returns the set of nodes read from network config.
      *
-     * @return set of CordVtnNodeConfig or null
+     * @return set of CordVtnNodeConfig or empty set
      */
     public Set<CordVtnNode> cordVtnNodes() {
 
@@ -68,7 +68,7 @@
         JsonNode jsonNodes = object.get(CORDVTN_NODES);
         if (jsonNodes == null) {
             log.debug("No CORD VTN nodes found");
-            return null;
+            return nodes;
         }
 
         for (JsonNode jsonNode : jsonNodes) {
@@ -94,7 +94,8 @@
                         TpPort.tpPort(Integer.parseInt(getConfig(object, OVSDB_PORT))),
                         sshInfo,
                         DeviceId.deviceId(getConfig(jsonNode, BRIDGE_ID)),
-                        getConfig(jsonNode, DATA_PLANE_INTF));
+                        getConfig(jsonNode, DATA_PLANE_INTF),
+                        CordVtnNodeState.noState());
 
                 log.info("Successfully read {} from the config", hostname);
                 nodes.add(newNode);
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 9ae8237..303c927 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2015 Open Networking Laboratory
+ * Copyright 2015-2016 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.
@@ -37,6 +37,7 @@
     private final SshAccessInfo sshInfo;
     private final DeviceId bridgeId;
     private final String dpIntf;
+    private final CordVtnNodeState state;
 
     public static final Comparator<CordVtnNode> CORDVTN_NODE_COMPARATOR =
             (node1, node2) -> node1.hostname().compareTo(node2.hostname());
@@ -52,10 +53,11 @@
      * @param sshInfo SSH access information
      * @param bridgeId integration bridge identifier
      * @param dpIntf data plane interface name
+     * @param state cordvtn node state
      */
     public CordVtnNode(String hostname, NetworkAddress hostMgmtIp, NetworkAddress localMgmtIp,
                        NetworkAddress dpIp, TpPort ovsdbPort, SshAccessInfo sshInfo,
-                       DeviceId bridgeId, String dpIntf) {
+                       DeviceId bridgeId, String dpIntf, CordVtnNodeState state) {
         this.hostname = checkNotNull(hostname, "hostname cannot be null");
         this.hostMgmtIp = checkNotNull(hostMgmtIp, "hostMgmtIp cannot be null");
         this.localMgmtIp = checkNotNull(localMgmtIp, "localMgmtIp cannot be null");
@@ -64,6 +66,23 @@
         this.sshInfo = checkNotNull(sshInfo, "sshInfo cannot be null");
         this.bridgeId = checkNotNull(bridgeId, "bridgeId cannot be null");
         this.dpIntf = checkNotNull(dpIntf, "dpIntf cannot be null");
+        this.state = state;
+    }
+
+    /**
+     * Returns cordvtn node with new state.
+     *
+     * @param node cordvtn node
+     * @param state cordvtn node init state
+     * @return cordvtn node
+     */
+    public static CordVtnNode getUpdatedNode(CordVtnNode node, CordVtnNodeState state) {
+        return new CordVtnNode(node.hostname,
+                               node.hostMgmtIp, node.localMgmtIp, node.dpIp,
+                               node.ovsdbPort,
+                               node.sshInfo,
+                               node.bridgeId,
+                               node.dpIntf, state);
     }
 
     /**
@@ -147,6 +166,15 @@
         return this.dpIntf;
     }
 
+    /**
+     * Returns the state of the node.
+     *
+     * @return state
+     */
+    public CordVtnNodeState state() {
+        return this.state;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
@@ -180,6 +208,7 @@
                 .add("sshInfo", sshInfo)
                 .add("bridgeId", bridgeId)
                 .add("dpIntf", dpIntf)
+                .add("state", state)
                 .toString();
     }
 }
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 4802db8..d4aceeb 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeManager.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeManager.java
@@ -72,6 +72,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
@@ -165,11 +166,11 @@
     private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
     private final BridgeHandler bridgeHandler = new BridgeHandler();
 
-    private ConsistentMap<CordVtnNode, NodeState> nodeStore;
+    private ConsistentMap<String, CordVtnNode> nodeStore;
     private CordVtnRuleInstaller ruleInstaller;
     private ApplicationId appId;
 
-    private enum NodeState {
+    private enum NodeState implements CordVtnNodeState {
 
         INIT {
             @Override
@@ -197,7 +198,6 @@
             public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
                 nodeManager.setIpAddress(node);
             }
-
         },
         COMPLETE {
             @Override
@@ -217,7 +217,7 @@
     @Activate
     protected void active() {
         appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
-        nodeStore = storageService.<CordVtnNode, NodeState>consistentMapBuilder()
+        nodeStore = storageService.<String, CordVtnNode>consistentMapBuilder()
                 .withSerializer(Serializer.using(NODE_SERIALIZER.build()))
                 .withName("cordvtn-nodestore")
                 .withApplicationId(appId)
@@ -252,7 +252,8 @@
     public void addNode(CordVtnNode node) {
         checkNotNull(node);
 
-        nodeStore.putIfAbsent(node, getNodeState(node));
+        // allow update node attributes
+        nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, getNodeState(node)));
         initNode(node);
     }
 
@@ -268,7 +269,7 @@
             disconnectOvsdb(node);
         }
 
-        nodeStore.remove(node);
+        nodeStore.remove(node.hostname());
     }
 
     /**
@@ -279,12 +280,13 @@
     public void initNode(CordVtnNode node) {
         checkNotNull(node);
 
-        if (!nodeStore.containsKey(node)) {
+        if (!nodeStore.containsKey(node.hostname())) {
             log.warn("Node {} does not exist, add node first", node.hostname());
             return;
         }
 
         NodeState state = getNodeState(node);
+        log.debug("Init node: {} state: {}", node.hostname(), state.toString());
         state.process(this, node);
     }
 
@@ -296,7 +298,7 @@
      */
     public boolean isNodeInitComplete(CordVtnNode node) {
         checkNotNull(node);
-        return nodeStore.containsKey(node) && getNodeState(node).equals(NodeState.COMPLETE);
+        return nodeStore.containsKey(node.hostname()) && getNodeState(node).equals(NodeState.COMPLETE);
     }
 
     /**
@@ -311,8 +313,9 @@
         // the state saved in nodeStore can be wrong if IP address settings are changed
         // after the node init has been completed since there's no way to detect it
         // getNodeState and checkNodeInitState always return correct answer but can be slow
-        Versioned<NodeState> state = nodeStore.get(node);
-        return state != null && state.value().equals(NodeState.COMPLETE);
+        Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
+        CordVtnNodeState state = versionedNode.value().state();
+        return state != null && state.equals(NodeState.COMPLETE);
     }
 
     /**
@@ -324,7 +327,7 @@
     public String checkNodeInitState(CordVtnNode node) {
         checkNotNull(node);
 
-        if (!nodeStore.containsKey(node)) {
+        if (!nodeStore.containsKey(node.hostname())) {
             log.warn("Node {} does not exist, add node first", node.hostname());
             return null;
         }
@@ -337,13 +340,13 @@
 
         Set<IpAddress> intBrIps = RemoteIpCommandUtil.getCurrentIps(session, DEFAULT_BRIDGE);
         String result = String.format(
-                "Integration bridge created/connected : %s (%s)%n" +
+                "br-int created and connected : %s (%s)%n" +
                         "VXLAN interface created : %s%n" +
                         "Data plane interface added : %s (%s)%n" +
                         "IP flushed from %s : %s%n" +
                         "Data plane IP added to br-int : %s (%s)%n" +
                         "Local management IP added to br-int : %s (%s)",
-                isBrIntCreated(node) ? OK : NO, DEFAULT_BRIDGE,
+                isBrIntCreated(node) ? OK : NO, node.intBrId(),
                 isTunnelIntfCreated(node) ? OK : NO,
                 isDataPlaneIntfAdded(node) ? OK : NO, node.dpIntf(),
                 node.dpIntf(),
@@ -371,9 +374,9 @@
      * @return list of nodes
      */
     public List<CordVtnNode> getNodes() {
-        List<CordVtnNode> nodes = new ArrayList<>();
-        nodes.addAll(nodeStore.keySet());
-        return nodes;
+        return nodeStore.values().stream()
+                .map(Versioned::value)
+                .collect(Collectors.toList());
     }
 
     /**
@@ -410,8 +413,7 @@
         checkNotNull(node);
 
         log.debug("Changed {} state: {}", node.hostname(), newState.toString());
-
-        nodeStore.put(node, newState);
+        nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
         newState.process(this, node);
     }
 
@@ -497,7 +499,7 @@
     private void connectOvsdb(CordVtnNode node) {
         checkNotNull(node);
 
-        if (!nodeStore.containsKey(node)) {
+        if (!nodeStore.containsKey(node.hostname())) {
             log.warn("Node {} does not exist", node.hostname());
             return;
         }
@@ -515,7 +517,7 @@
     private void disconnectOvsdb(CordVtnNode node) {
         checkNotNull(node);
 
-        if (!nodeStore.containsKey(node)) {
+        if (!nodeStore.containsKey(node.hostname())) {
             log.warn("Node {} does not exist", node.hostname());
             return;
         }
@@ -747,6 +749,7 @@
         @Override
         public void disconnected(Device device) {
             if (!deviceService.isAvailable(device.id())) {
+                log.debug("Device {} is disconnected", device.id());
                 adminService.removeDevice(device.id());
             }
         }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeState.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeState.java
new file mode 100644
index 0000000..7541552
--- /dev/null
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNodeState.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 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.cordvtn;
+
+/**
+ * Entity that defines possible init state of the cordvtn node.
+ */
+public interface CordVtnNodeState {
+    /**
+     * Returns null for no state.
+     *
+     * @return null
+     */
+    static CordVtnNodeState noState() {
+        return null;
+    }
+}