Support DNS storage discovery via cluster metadata

Change-Id: I62499cb8d6bff57d780e7aaaf07eaa84ec332192
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java b/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java
index bf3e4a0..521e20a 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java
@@ -41,10 +41,14 @@
  */
 public final class ClusterMetadata implements Provided {
 
+    private static final ProviderId NONE_PROVIDER_ID = new ProviderId("none", "none");
+    private static final String DEFAULT_CLUSTER_SECRET = "INSECURE!";
+
     private final ProviderId providerId;
     private final String name;
     private final ControllerNode localNode;
     private final Set<ControllerNode> controllerNodes;
+    private final String storageDnsService;
     private final Set<Node> storageNodes;
     private final String clusterSecret;
 
@@ -62,6 +66,7 @@
         name = null;
         localNode = null;
         controllerNodes = null;
+        storageDnsService = null;
         storageNodes = null;
         clusterSecret = null;
     }
@@ -72,6 +77,7 @@
      * @param name The cluster Name
      * @param localNode The local node
      * @param controllerNodes Set of nodes in cluster
+     * @param storageDnsService The storage DNS service name
      * @param storageNodes Set of storage nodes
      */
     @Deprecated
@@ -80,13 +86,19 @@
             String name,
             ControllerNode localNode,
             Set<ControllerNode> controllerNodes,
+            String storageDnsService,
             Set<Node> storageNodes) {
-        this.providerId = checkNotNull(providerId);
-        this.name = checkNotNull(name);
-        this.localNode = localNode;
-        this.controllerNodes = ImmutableSet.copyOf(checkNotNull(controllerNodes));
-        this.storageNodes = ImmutableSet.copyOf(checkNotNull(storageNodes));
-        this.clusterSecret = "INSECURE!";
+        this(providerId, name, localNode, controllerNodes, storageDnsService, storageNodes, DEFAULT_CLUSTER_SECRET);
+    }
+
+    public ClusterMetadata(
+            ProviderId providerId,
+            String name,
+            ControllerNode localNode,
+            Set<ControllerNode> controllerNodes,
+            Set<Node> storageNodes,
+            String clusterSecret) {
+        this(providerId, name, localNode, controllerNodes, null, storageNodes, clusterSecret);
     }
 
     public ClusterMetadata(
@@ -94,12 +106,14 @@
         String name,
         ControllerNode localNode,
         Set<ControllerNode> controllerNodes,
+        String storageDnsService,
         Set<Node> storageNodes,
         String clusterSecret) {
         this.providerId = checkNotNull(providerId);
         this.name = checkNotNull(name);
         this.localNode = localNode;
         this.controllerNodes = ImmutableSet.copyOf(checkNotNull(controllerNodes));
+        this.storageDnsService = storageDnsService;
         this.storageNodes = ImmutableSet.copyOf(checkNotNull(storageNodes));
         this.clusterSecret = clusterSecret;
     }
@@ -113,14 +127,20 @@
      */
     @Deprecated
     public ClusterMetadata(
-            String name, ControllerNode localNode, Set<ControllerNode> controllerNodes, Set<Node> storageNodes) {
-        this(new ProviderId("none", "none"), name, localNode, controllerNodes, storageNodes, "INSECURE!");
+            String name,
+            ControllerNode localNode,
+            Set<ControllerNode> controllerNodes,
+            Set<Node> storageNodes) {
+        this(NONE_PROVIDER_ID, name, localNode, controllerNodes, null, storageNodes, DEFAULT_CLUSTER_SECRET);
     }
 
     public ClusterMetadata(
-            String name, ControllerNode localNode, Set<ControllerNode> controllerNodes, Set<Node> storageNodes,
+            String name,
+            ControllerNode localNode,
+            Set<ControllerNode> controllerNodes,
+            Set<Node> storageNodes,
             String clusterSecret) {
-        this(new ProviderId("none", "none"), name, localNode, controllerNodes, storageNodes, clusterSecret);
+        this(NONE_PROVIDER_ID, name, localNode, controllerNodes, null, storageNodes, clusterSecret);
     }
 
     @Override
@@ -138,6 +158,15 @@
     }
 
     /**
+     * Returns the DNS service through which to locate storage nodes.
+     *
+     * @return the DNS service through which to locate storage nodes
+     */
+    public String getStorageDnsService() {
+        return storageDnsService;
+    }
+
+    /**
      * Returns the local controller node.
      * @return the local controller node
      */
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 807a143..f3711e2 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
@@ -117,6 +117,7 @@
                 .stream()
                 .map(this::toPrototype)
                 .collect(Collectors.toSet()));
+        prototype.setStorageDnsService(metadata.getStorageDnsService());
         prototype.setStorage(metadata.getStorageNodes()
                 .stream()
                 .map(this::toPrototype)
@@ -274,6 +275,7 @@
                         .stream()
                         .map(node -> new DefaultControllerNode(getNodeId(node), getNodeHost(node), getNodePort(node)))
                         .collect(Collectors.toSet()),
+                    metadata.getStorageDnsService(),
                     metadata.getStorage()
                         .stream()
                         .map(node -> new DefaultControllerNode(getNodeId(node), getNodeHost(node), getNodePort(node)))
@@ -310,6 +312,7 @@
         private String name;
         private NodePrototype node;
         private Set<NodePrototype> controller = Sets.newHashSet();
+        private String storageDnsService;
         private Set<NodePrototype> storage = Sets.newHashSet();
         private String clusterSecret;
 
@@ -337,6 +340,14 @@
             this.controller = controller;
         }
 
+        public String getStorageDnsService() {
+            return storageDnsService;
+        }
+
+        public void setStorageDnsService(String storageDnsService) {
+            this.storageDnsService = storageDnsService;
+        }
+
         public Set<NodePrototype> getStorage() {
             return storage;
         }
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 c46ce87..3f2edf5 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
@@ -17,6 +17,8 @@
 
 import io.atomix.cluster.Node;
 import io.atomix.cluster.discovery.BootstrapDiscoveryProvider;
+import io.atomix.cluster.discovery.DnsDiscoveryProvider;
+import io.atomix.cluster.discovery.NodeDiscoveryProvider;
 import io.atomix.core.Atomix;
 import io.atomix.protocols.raft.partition.RaftPartitionGroup;
 import org.onosproject.cluster.ClusterMetadata;
@@ -72,6 +74,26 @@
 
     private Atomix createAtomix() {
         ClusterMetadata metadata = metadataService.getClusterMetadata();
+
+        // If a storage DNS service was provided, use the DNS service for service discovery.
+        // Otherwise, use a static list of storage nodes.
+        NodeDiscoveryProvider discovery;
+        if (metadata.getStorageDnsService() != null) {
+            discovery = DnsDiscoveryProvider.builder()
+                .withService(metadata.getStorageDnsService())
+                .build();
+        } else {
+            discovery = BootstrapDiscoveryProvider.builder()
+                .withNodes(metadata.getStorageNodes().stream()
+                    .map(node -> Node.builder()
+                        .withId(node.id().id())
+                        .withHost(node.host())
+                        .withPort(node.tcpPort())
+                        .build())
+                    .collect(Collectors.toList()))
+                .build();
+        }
+
         if (!metadata.getStorageNodes().isEmpty()) {
             // If storage nodes are defined, construct an instance that connects to them for service discovery.
             return Atomix.builder(getClass().getClassLoader())
@@ -80,15 +102,7 @@
                 .withHost(metadata.getLocalNode().host())
                 .withPort(metadata.getLocalNode().tcpPort())
                 .withProperty("type", "onos")
-                .withMembershipProvider(BootstrapDiscoveryProvider.builder()
-                    .withNodes(metadata.getStorageNodes().stream()
-                        .map(node -> Node.builder()
-                            .withId(node.id().id())
-                            .withHost(node.host())
-                            .withPort(node.tcpPort())
-                            .build())
-                        .collect(Collectors.toList()))
-                    .build())
+                .withMembershipProvider(discovery)
                 .build();
         } else {
             log.warn("No storage nodes found in cluster metadata!");
@@ -107,15 +121,7 @@
                 .withHost(metadata.getLocalNode().host())
                 .withPort(metadata.getLocalNode().tcpPort())
                 .withProperty("type", "onos")
-                .withMembershipProvider(BootstrapDiscoveryProvider.builder()
-                    .withNodes(metadata.getControllerNodes().stream()
-                        .map(node -> io.atomix.cluster.Node.builder()
-                            .withId(node.id().id())
-                            .withHost(node.host())
-                            .withPort(node.tcpPort())
-                            .build())
-                        .collect(Collectors.toList()))
-                    .build())
+                .withMembershipProvider(discovery)
                 .withManagementGroup(RaftPartitionGroup.builder("system")
                     .withNumPartitions(1)
                     .withDataDirectory(new File(LOCAL_DATA_DIR, "system"))