Support hostname resolution for cluster configuration

Change-Id: I9afb97bfba05366fa63dc7b9022b914bd2d1cce0
(cherry picked from commit 97cd95d5a2a7a1bf1f3bfd3091f2d66f747eee33)
diff --git a/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java b/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java
index fa6ffc9..b366823 100644
--- a/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java
+++ b/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java
@@ -103,6 +103,16 @@
         }
 
         @Override
+        public IpAddress ip(boolean resolve) {
+            return null;
+        }
+
+        @Override
+        public String host() {
+            return null;
+        }
+
+        @Override
         public int tcpPort() {
             return 0;
         }
diff --git a/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java b/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java
index 7c48680..ebd918b 100644
--- a/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java
+++ b/apps/cpman/app/src/test/java/org/onosproject/cpman/rest/ControlMetricsResourceTest.java
@@ -88,6 +88,16 @@
         }
 
         @Override
+        public IpAddress ip(boolean resolve) {
+            return null;
+        }
+
+        @Override
+        public String host() {
+            return null;
+        }
+
+        @Override
         public int tcpPort() {
             return 0;
         }
diff --git a/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java b/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java
index d7df71b..50b0bdb 100644
--- a/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java
+++ b/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java
@@ -618,6 +618,16 @@
                 }
 
                 @Override
+                public IpAddress ip(boolean resolve) {
+                    return null;
+                }
+
+                @Override
+                public String host() {
+                    return null;
+                }
+
+                @Override
                 public int tcpPort() {
                     return 0;
                 }
diff --git a/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java b/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
index 7c324f4..8bc4757 100644
--- a/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
+++ b/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
@@ -15,10 +15,12 @@
  */
 package org.onosproject.cluster;
 
-import org.onlab.packet.IpAddress;
-
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.Objects;
 
+import org.onlab.packet.IpAddress;
+
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -30,13 +32,14 @@
     public static final int DEFAULT_PORT = 9876;
 
     private final NodeId id;
-    private final IpAddress ip;
+    private final String host;
     private final int tcpPort;
+    private transient volatile IpAddress ip;
 
     // For serialization
     private DefaultControllerNode() {
         this.id = null;
-        this.ip = null;
+        this.host = null;
         this.tcpPort = 0;
     }
 
@@ -44,23 +47,44 @@
      * Creates a new instance with the specified id and IP address.
      *
      * @param id instance identifier
+     * @param host instance hostname
+     */
+    public DefaultControllerNode(NodeId id, String host) {
+        this(id, host, DEFAULT_PORT);
+    }
+
+    /**
+     * Creates a new instance with the specified id and IP address and TCP port.
+     *
+     * @param id instance identifier
+     * @param host instance host name
+     * @param tcpPort TCP port
+     */
+    public DefaultControllerNode(NodeId id, String host, int tcpPort) {
+        this.id = checkNotNull(id);
+        this.host = host;
+        this.tcpPort = tcpPort;
+    }
+
+    /**
+     * Creates a new instance with the specified id and IP address.
+     *
+     * @param id instance identifier
      * @param ip instance IP address
      */
     public DefaultControllerNode(NodeId id, IpAddress ip) {
-        this(id, ip, DEFAULT_PORT);
+        this(id, ip != null ? ip.toString() : null, DEFAULT_PORT);
     }
 
     /**
-     * Creates a new instance with the specified id and IP address and TCP port.
+     * Creates a new instance with the specified id and IP address.
      *
      * @param id instance identifier
      * @param ip instance IP address
      * @param tcpPort TCP port
      */
     public DefaultControllerNode(NodeId id, IpAddress ip, int tcpPort) {
-        this.id = checkNotNull(id);
-        this.ip = ip;
-        this.tcpPort = tcpPort;
+        this(id, ip != null ? ip.toString() : null, tcpPort);
     }
 
     @Override
@@ -69,10 +93,35 @@
     }
 
     @Override
-    public IpAddress ip() {
+    public String host() {
+        return host;
+    }
+
+    @Override
+    public IpAddress ip(boolean resolve) {
+        if (resolve) {
+            ip = resolveIp();
+            return ip;
+        }
+
+        if (ip == null) {
+            synchronized (this) {
+                if (ip == null) {
+                    ip = resolveIp();
+                }
+            }
+        }
         return ip;
     }
 
+    private IpAddress resolveIp() {
+        try {
+            return IpAddress.valueOf(InetAddress.getByName(host));
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
     @Override
     public int tcpPort() {
         return tcpPort;
@@ -97,8 +146,11 @@
 
     @Override
     public String toString() {
-        return toStringHelper(this).add("id", id)
-                .add("ip", ip).add("tcpPort", tcpPort).toString();
+        return toStringHelper(this)
+            .add("id", id)
+            .add("host", host)
+            .add("tcpPort", tcpPort)
+            .toString();
     }
 
 }
diff --git a/core/api/src/main/java/org/onosproject/cluster/Node.java b/core/api/src/main/java/org/onosproject/cluster/Node.java
index 8d6a197..5adc076 100644
--- a/core/api/src/main/java/org/onosproject/cluster/Node.java
+++ b/core/api/src/main/java/org/onosproject/cluster/Node.java
@@ -34,7 +34,24 @@
      *
      * @return IP address
      */
-    IpAddress ip();
+    default IpAddress ip() {
+        return ip(false);
+    }
+
+    /**
+     * Returns the IP address of the controller instance.
+     *
+     * @param resolve whether to resolve the hostname
+     * @return IP address
+     */
+    IpAddress ip(boolean resolve);
+
+    /**
+     * Returns the host name of the controller instance.
+     *
+     * @return the host name of the controller instance
+     */
+    String host();
 
     /**
      * Returns the TCP port on which the node listens for connections.
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java b/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
index e93ce33..d5ef232 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
@@ -35,7 +35,6 @@
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onlab.packet.IpAddress;
 import org.onosproject.cluster.ClusterMetadata;
 import org.onosproject.cluster.ClusterMetadataProvider;
 import org.onosproject.cluster.ClusterMetadataProviderRegistry;
@@ -126,7 +125,7 @@
     private NodePrototype toPrototype(Node node) {
         NodePrototype prototype = new NodePrototype();
         prototype.setId(node.id().id());
-        prototype.setIp(node.ip().toString());
+        prototype.setHost(node.host());
         prototype.setPort(node.tcpPort());
         return prototype;
     }
@@ -198,6 +197,36 @@
         }
     }
 
+    private static NodeId getNodeId(NodePrototype node) {
+        if (node.getId() != null) {
+            return NodeId.nodeId(node.getId());
+        } else if (node.getHost() != null) {
+            return NodeId.nodeId(node.getHost());
+        } else if (node.getIp() != null) {
+            return NodeId.nodeId(node.getIp());
+        } else {
+            return NodeId.nodeId(UUID.randomUUID().toString());
+        }
+    }
+
+    private static String getNodeHost(NodePrototype node) {
+        if (node.getHost() != null) {
+            return node.getHost();
+        } else if (node.getIp() != null) {
+            return node.getIp();
+        } else {
+            throw new IllegalArgumentException(
+                "Malformed cluster configuration: No host specified for node " + node.getId());
+        }
+    }
+
+    private static int getNodePort(NodePrototype node) {
+        if (node.getPort() != null) {
+            return node.getPort();
+        }
+        return DefaultControllerNode.DEFAULT_PORT;
+    }
+
     private Versioned<ClusterMetadata> fetchMetadata(String metadataUrl) {
         try {
             URL url = new URL(metadataUrl);
@@ -235,30 +264,16 @@
                 metadata.getName(),
                 metadata.getNode() != null ?
                     new DefaultControllerNode(
-                        metadata.getNode().getId() != null
-                            ? NodeId.nodeId(metadata.getNode().getId())
-                            : metadata.getNode().getIp() != null
-                            ? NodeId.nodeId(IpAddress.valueOf(metadata.getNode().getIp()).toString())
-                            : NodeId.nodeId(UUID.randomUUID().toString()),
-                        metadata.getNode().getIp() != null
-                            ? IpAddress.valueOf(metadata.getNode().getIp())
-                            : null,
-                        metadata.getNode().getPort() != null
-                            ? metadata.getNode().getPort()
-                            : DefaultControllerNode.DEFAULT_PORT) : null,
+                        getNodeId(metadata.getNode()),
+                        getNodeHost(metadata.getNode()),
+                        getNodePort(metadata.getNode())) : null,
                     metadata.getController()
                         .stream()
-                        .map(node -> new DefaultControllerNode(
-                            NodeId.nodeId(node.getId()),
-                            IpAddress.valueOf(node.getIp()),
-                            node.getPort() != null ? node.getPort() : 5679))
+                        .map(node -> new DefaultControllerNode(getNodeId(node), getNodeHost(node), getNodePort(node)))
                         .collect(Collectors.toSet()),
                     metadata.getStorage()
                         .stream()
-                        .map(node -> new DefaultControllerNode(
-                            NodeId.nodeId(node.getId()),
-                            IpAddress.valueOf(node.getIp()),
-                            node.getPort() != null ? node.getPort() : 5679))
+                        .map(node -> new DefaultControllerNode(getNodeId(node), getNodeHost(node), getNodePort(node)))
                         .collect(Collectors.toSet())),
                 version);
         } catch (IOException e) {
@@ -329,6 +344,7 @@
     private static class NodePrototype {
         private String id;
         private String ip;
+        private String host;
         private Integer port;
 
         public String getId() {
@@ -347,6 +363,14 @@
             this.ip = ip;
         }
 
+        public String getHost() {
+            return host;
+        }
+
+        public void setHost(String host) {
+            this.host = host;
+        }
+
         public Integer getPort() {
             return port;
         }
diff --git a/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManagerTest.java b/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManagerTest.java
index ac8a9bc..8cfa7ff 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManagerTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManagerTest.java
@@ -257,11 +257,21 @@
                     }
 
                     @Override
+                    public String host() {
+                        return ipAddress;
+                    }
+
+                    @Override
                     public IpAddress ip() {
                         return IpAddress.valueOf(ipAddress);
                     }
 
                     @Override
+                    public IpAddress ip(boolean resolve) {
+                        return ip();
+                    }
+
+                    @Override
                     public int tcpPort() {
                         return ep.port();
                     }
diff --git a/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java
index ceac0c2..9b9c4db 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/flow/impl/DistributedFlowRuleStoreTest.java
@@ -25,6 +25,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.NodeId;
@@ -126,11 +127,21 @@
         }
 
         @Override
+        public String host() {
+            return "127.0.0.1";
+        }
+
+        @Override
         public Ip4Address ip() {
             return Ip4Address.valueOf("127.0.0.1");
         }
 
         @Override
+        public IpAddress ip(boolean resolve) {
+            return ip();
+        }
+
+        @Override
         public int tcpPort() {
             return 0;
         }
diff --git a/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ECFlowRuleStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ECFlowRuleStoreTest.java
index 72562f9..0dfc2a2 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ECFlowRuleStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/flow/impl/ECFlowRuleStoreTest.java
@@ -22,6 +22,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import org.onlab.packet.IpAddress;
 import org.onosproject.cfg.ComponentConfigAdapter;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.cluster.ClusterService;
@@ -137,11 +138,21 @@
         }
 
         @Override
+        public String host() {
+            return "127.0.0.1";
+        }
+
+        @Override
         public Ip4Address ip() {
             return Ip4Address.valueOf("127.0.0.1");
         }
 
         @Override
+        public IpAddress ip(boolean resolve) {
+            return ip();
+        }
+
+        @Override
         public int tcpPort() {
             return 0;
         }
diff --git a/core/store/dist/src/test/java/org/onosproject/store/statistic/impl/DistributedStatisticStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/statistic/impl/DistributedStatisticStoreTest.java
index 4691df9..ad4baf7 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/statistic/impl/DistributedStatisticStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/statistic/impl/DistributedStatisticStoreTest.java
@@ -135,7 +135,12 @@
         }
 
         @Override
-        public Ip4Address ip() {
+        public String host() {
+            return "127.0.0.1";
+        }
+
+        @Override
+        public Ip4Address ip(boolean resolve) {
             return Ip4Address.valueOf("127.0.0.1");
         }
 
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/atomix/impl/AtomixManager.java b/core/store/primitives/src/main/java/org/onosproject/store/atomix/impl/AtomixManager.java
index ba66372..8df1a40 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/atomix/impl/AtomixManager.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/atomix/impl/AtomixManager.java
@@ -78,13 +78,13 @@
             return Atomix.builder(getClass().getClassLoader())
                 .withClusterId(metadata.getName())
                 .withMemberId(metadataService.getLocalNode().id().id())
-                .withAddress(metadataService.getLocalNode().ip().toString(), metadataService.getLocalNode().tcpPort())
+                .withAddress(metadataService.getLocalNode().host(), metadataService.getLocalNode().tcpPort())
                 .withProperty("type", "onos")
                 .withMembershipProvider(BootstrapDiscoveryProvider.builder()
                     .withNodes(metadata.getStorageNodes().stream()
                         .map(node -> io.atomix.cluster.Node.builder()
                             .withId(node.id().id())
-                            .withAddress(node.ip().toString(), node.tcpPort())
+                            .withAddress(node.host(), node.tcpPort())
                             .build())
                         .collect(Collectors.toList()))
                     .build())
@@ -103,13 +103,13 @@
             return Atomix.builder(getClass().getClassLoader())
                 .withClusterId(metadata.getName())
                 .withMemberId(metadataService.getLocalNode().id().id())
-                .withAddress(metadataService.getLocalNode().ip().toString(), metadataService.getLocalNode().tcpPort())
+                .withAddress(metadataService.getLocalNode().host(), metadataService.getLocalNode().tcpPort())
                 .withProperty("type", "onos")
                 .withMembershipProvider(BootstrapDiscoveryProvider.builder()
                     .withNodes(metadata.getControllerNodes().stream()
                         .map(node -> io.atomix.cluster.Node.builder()
                             .withId(node.id().id())
-                            .withAddress(node.ip().toString(), node.tcpPort())
+                            .withAddress(node.host(), node.tcpPort())
                             .build())
                         .collect(Collectors.toList()))
                     .build())