[ONOS-7757] Support onos-local and embedded cluster configurations
- Refactor cluster.json to support internal/external nodes ('controller' and 'storage')
- Bootstrap embedded partitions when 'storage' nodes not present
- Update onos-gen-config script to generate cluster.json based on environment variables
- Update setup scenario to ignore missing $OCC# environment variables

Change-Id: Ia93b64e13d7a7c35ed712da4c681425e3ccf9fe9
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 24b86f5..ba66372 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
@@ -15,16 +15,21 @@
  */
 package org.onosproject.store.atomix.impl;
 
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import io.atomix.cluster.discovery.BootstrapDiscoveryProvider;
 import io.atomix.core.Atomix;
+import io.atomix.protocols.raft.partition.RaftPartitionGroup;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterMetadata;
 import org.onosproject.cluster.ClusterMetadataService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,7 +40,7 @@
 @Component(immediate = true)
 @Service(value = AtomixManager.class)
 public class AtomixManager {
-
+    private static final String LOCAL_DATA_DIR = System.getProperty("karaf.data") + "/db/partitions/";
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -54,6 +59,7 @@
 
     @Activate
     public void activate() {
+        log.info("{}", metadataService.getClusterMetadata());
         atomix = createAtomix();
         atomix.start().join();
         log.info("Started");
@@ -66,19 +72,58 @@
     }
 
     private Atomix createAtomix() {
-        return Atomix.builder(getClass().getClassLoader())
-            .withClusterId(metadataService.getClusterMetadata().getName())
-            .withMemberId(metadataService.getLocalNode().id().id())
-            .withAddress(metadataService.getLocalNode().ip().toString(), metadataService.getLocalNode().tcpPort())
-            .withProperty("type", "onos")
-            .withMembershipProvider(BootstrapDiscoveryProvider.builder()
-                .withNodes(metadataService.getClusterMetadata().getStorageNodes().stream()
-                    .map(node -> io.atomix.cluster.Node.builder()
-                        .withId(node.id().id())
-                        .withAddress(node.ip().toString(), node.tcpPort())
-                        .build())
-                    .collect(Collectors.toList()))
-                .build())
-            .build();
+        ClusterMetadata metadata = metadataService.getClusterMetadata();
+        if (!metadata.getStorageNodes().isEmpty()) {
+            // If storage nodes are defined, construct an instance that connects to them for service discovery.
+            return Atomix.builder(getClass().getClassLoader())
+                .withClusterId(metadata.getName())
+                .withMemberId(metadataService.getLocalNode().id().id())
+                .withAddress(metadataService.getLocalNode().ip().toString(), 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())
+                            .build())
+                        .collect(Collectors.toList()))
+                    .build())
+                .build();
+        } else {
+            log.warn("No storage nodes found in cluster metadata!");
+            log.warn("Bootstrapping ONOS cluster in test mode! For production use, configure external storage nodes.");
+
+            // If storage nodes are not defined, construct a local instance with a Raft partition group.
+            List<String> raftMembers = !metadata.getControllerNodes().isEmpty()
+                ? metadata.getControllerNodes()
+                .stream()
+                .map(node -> node.id().id())
+                .collect(Collectors.toList())
+                : Collections.singletonList(metadataService.getLocalNode().id().id());
+            return Atomix.builder(getClass().getClassLoader())
+                .withClusterId(metadata.getName())
+                .withMemberId(metadataService.getLocalNode().id().id())
+                .withAddress(metadataService.getLocalNode().ip().toString(), 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())
+                            .build())
+                        .collect(Collectors.toList()))
+                    .build())
+                .withManagementGroup(RaftPartitionGroup.builder("system")
+                    .withNumPartitions(1)
+                    .withDataDirectory(new File(LOCAL_DATA_DIR, "system"))
+                    .withMembers(raftMembers)
+                    .build())
+                .addPartitionGroup(RaftPartitionGroup.builder("raft")
+                    .withNumPartitions(raftMembers.size())
+                    .withDataDirectory(new File(LOCAL_DATA_DIR, "data"))
+                    .withMembers(raftMembers)
+                    .build())
+                .build();
+        }
     }
 }