[ONOS-5184] Allow configuring cluster partition size

Adds a new parameter to onos-form-cluster: -s <partition-size>
to allow specifying the partition size to be used when creating
the cluster.

Change-Id: I4c31d6e97fe0fd811831296f41a09160bebb58de
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java b/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
index 6b7d614..179adfc 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
@@ -34,6 +34,16 @@
     void formCluster(Set<ControllerNode> nodes);
 
     /**
+     * Forms cluster configuration based on the specified set of node
+     * information.&nbsp; This method resets and restarts the controller
+     * instance.
+     *
+     * @param nodes    set of nodes that form the cluster
+     * @param partitionSize number of nodes to compose a partition
+     */
+    void formCluster(Set<ControllerNode> nodes, int partitionSize);
+
+    /**
      * Adds a new controller node to the cluster.
      *
      * @param nodeId  controller node identifier
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java b/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
index 0041547..a2b9000 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
@@ -71,6 +71,7 @@
         implements ClusterService, ClusterAdminService {
 
     public static final String INSTANCE_ID_NULL = "Instance ID cannot be null";
+    private static final int DEFAULT_PARTITION_SIZE = 3;
     private final Logger log = getLogger(getClass());
 
     private ClusterStoreDelegate delegate = new InternalStoreDelegate();
@@ -146,10 +147,15 @@
 
     @Override
     public void formCluster(Set<ControllerNode> nodes) {
+        formCluster(nodes, DEFAULT_PARTITION_SIZE);
+    }
+
+    @Override
+    public void formCluster(Set<ControllerNode> nodes, int partitionSize) {
         checkNotNull(nodes, "Nodes cannot be null");
         checkArgument(!nodes.isEmpty(), "Nodes cannot be empty");
 
-        ClusterMetadata metadata = new ClusterMetadata("default", nodes, buildDefaultPartitions(nodes));
+        ClusterMetadata metadata = new ClusterMetadata("default", nodes, buildDefaultPartitions(nodes, partitionSize));
         clusterMetadataAdminService.setClusterMetadata(metadata);
         try {
             log.warn("Shutting down container for cluster reconfiguration!");
@@ -183,13 +189,13 @@
         }
     }
 
-    private static Set<Partition> buildDefaultPartitions(Collection<ControllerNode> nodes) {
+    private static Set<Partition> buildDefaultPartitions(Collection<ControllerNode> nodes, int partitionSize) {
         List<ControllerNode> sorted = new ArrayList<>(nodes);
         Collections.sort(sorted, (o1, o2) -> o1.id().toString().compareTo(o2.id().toString()));
         Set<Partition> partitions = Sets.newHashSet();
         // add partitions
         int length = nodes.size();
-        int count = Math.min(3, length);
+        int count = Math.min(partitionSize, length);
         for (int i = 0; i < length; i++) {
             int index = i;
             Set<NodeId> set = new HashSet<>(count);
diff --git a/tools/package/bin/onos-form-cluster b/tools/package/bin/onos-form-cluster
index 578a443..9286d0c 100755
--- a/tools/package/bin/onos-form-cluster
+++ b/tools/package/bin/onos-form-cluster
@@ -6,10 +6,11 @@
 [ $# -lt 2 ] && echo "usage: $(basename $0) ip1 ip2..." && exit 1
 
 # Scan arguments for user/password or other options...
-while getopts u:p: o; do
+while getopts u:p:s: o; do
     case "$o" in
         u) user=$OPTARG;;
         p) password=$OPTARG;;
+        s) partitionsize=$OPTARG;;
     esac
 done
 ONOS_WEB_USER=${ONOS_WEB_USER:-onos} # ONOS WEB User defaults to 'onos'
@@ -32,7 +33,11 @@
 for node in $nodes; do
     echo ", { \"ip\": \"$node\" }" >> $aux
 done
-echo "], \"ipPrefix\": \"$ipPrefix.*\" }" >> $aux
+echo "], \"ipPrefix\": \"$ipPrefix.*\"" >> $aux
+if ! [ -z ${partitionsize} ]; then
+    echo ", \"partitionSize\": $partitionsize" >> $aux
+fi
+echo " }" >> $aux
 
 for node in $ip $nodes; do
     echo "Forming cluster on $node..."
diff --git a/tools/test/bin/onos-form-cluster b/tools/test/bin/onos-form-cluster
index d0672db..a9f6a5b 100755
--- a/tools/test/bin/onos-form-cluster
+++ b/tools/test/bin/onos-form-cluster
@@ -7,10 +7,11 @@
 . $ONOS_ROOT/tools/build/envDefaults
 
 # Scan arguments for user/password or other options...
-while getopts u:p: o; do
+while getopts u:p:s: o; do
     case "$o" in
         u) user=$OPTARG;;
         p) password=$OPTARG;;
+        s) partitionsize=$OPTARG;;
     esac
 done
 ONOS_WEB_USER=${ONOS_WEB_USER:-onos} # ONOS WEB User defaults to 'onos'
@@ -29,6 +30,12 @@
     nodes="$@"
 fi
 
+if ! [ -z ${partitionsize} ]; then
+    partitionarg="-s ${partitionsize}"
+else
+    partitionarg=
+fi
+
 set -x
 
-ssh $ONOS_USER@$node $ONOS_INSTALL_DIR/bin/onos-form-cluster -u $user -p $password $nodes
+ssh $ONOS_USER@$node $ONOS_INSTALL_DIR/bin/onos-form-cluster -u $user -p $partitionarg $password $nodes
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java
index 250fd02..63a4081 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.rest.resources;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onosproject.cluster.ClusterAdminService;
@@ -94,7 +95,16 @@
         ObjectNode root = (ObjectNode) mapper().readTree(config);
 
         List<ControllerNode> nodes = codec.decode((ArrayNode) root.path("nodes"), this);
-        get(ClusterAdminService.class).formCluster(new HashSet<>(nodes));
+        JsonNode partitionSizeNode = root.get("partitionSize");
+        if (partitionSizeNode != null) {
+            int partitionSize = partitionSizeNode.asInt();
+            if (partitionSize == 0) {
+                return Response.notAcceptable(null).build();
+            }
+            get(ClusterAdminService.class).formCluster(new HashSet<>(nodes), partitionSize);
+        } else {
+            get(ClusterAdminService.class).formCluster(new HashSet<>(nodes));
+        }
 
         return Response.ok().build();
     }