Upgrade to Atomix 3.0-rc5
* Upgrade Raft primitives to Atomix 3.0
* Replace cluster store and messaging implementations with Atomix cluster management/messaging
* Add test scripts for installing/starting Atomix cluster
* Replace core primitives with Atomix primitives.

Change-Id: I7623653c81292a34f21b01f5f38ca11b5ef15cad
diff --git a/tools/build/bazel/generate_workspace.bzl b/tools/build/bazel/generate_workspace.bzl
index 19232ff..744598a 100644
--- a/tools/build/bazel/generate_workspace.bzl
+++ b/tools/build/bazel/generate_workspace.bzl
@@ -1,4 +1,4 @@
-# ***** This file was auto-generated at Mon, 23 Jul 2018 20:11:05 GMT. Do not edit this file manually. *****
+# ***** This file was auto-generated at Thu, 26 Jul 2018 18:15:57 GMT. Do not edit this file manually. *****
 # ***** Use onos-lib-gen *****
 
 load("//tools/build/bazel:variables.bzl", "ONOS_GROUP_ID", "ONOS_VERSION")
@@ -118,6 +118,18 @@
     "@javax_ws_rs_api//jar",
     "//utils/rest:onlab-rest",
 ]
+ATOMIX = [
+    "@atomix//jar",
+    "@atomix_cluster//jar",
+    "@atomix_gossip//jar",
+    "@atomix_primary_backup//jar",
+    "@atomix_primitive//jar",
+    "@atomix_raft//jar",
+    "@atomix_storage//jar",
+    "@atomix_utils//jar",
+    "@typesafe_config//jar",
+    "@fast_classpath_scanner//jar",
+]
 
 def generated_maven_jars():
     native.maven_jar(
@@ -140,8 +152,50 @@
 
     native.maven_jar(
         name = "atomix",
-        artifact = "io.atomix:atomix:2.0.23",
-        sha1 = "6b41cebf257e0094c2276b6fa589b2c73aff5f99",
+        artifact = "io.atomix:atomix:3.0.0-rc5",
+        sha1 = "0607a760f048f66645a35bcd8d5cfd96634af622",
+    )
+
+    native.maven_jar(
+        name = "atomix_cluster",
+        artifact = "io.atomix:atomix-cluster:3.0.0-rc5",
+        sha1 = "586badbad8e1b7727f260bd53f3c9487eda64191",
+    )
+
+    native.maven_jar(
+        name = "atomix_gossip",
+        artifact = "io.atomix:atomix-gossip:3.0.0-rc5",
+        sha1 = "e168926801b01f6d543ef4232c02b1c798c81edf",
+    )
+
+    native.maven_jar(
+        name = "atomix_primary_backup",
+        artifact = "io.atomix:atomix-primary-backup:3.0.0-rc5",
+        sha1 = "d339992903d53d7608957471ee9c97a45945d730",
+    )
+
+    native.maven_jar(
+        name = "atomix_primitive",
+        artifact = "io.atomix:atomix-primitive:3.0.0-rc5",
+        sha1 = "6606d4619a74b054e58d4478f43616a608803216",
+    )
+
+    native.maven_jar(
+        name = "atomix_raft",
+        artifact = "io.atomix:atomix-raft:3.0.0-rc5",
+        sha1 = "07c64d7be64ba81a4b53a55bf29d415db4998132",
+    )
+
+    native.maven_jar(
+        name = "atomix_storage",
+        artifact = "io.atomix:atomix-storage:3.0.0-rc5",
+        sha1 = "2e093d7d42de9cdf274675358a17c76e1fd36a5e",
+    )
+
+    native.maven_jar(
+        name = "atomix_utils",
+        artifact = "io.atomix:atomix-utils:3.0.0-rc5",
+        sha1 = "3435cf4ad6abfa85db210366bcaf0c6ac0ae7ec2",
     )
 
     native.maven_jar(
@@ -187,6 +241,12 @@
     )
 
     native.maven_jar(
+        name = "fast_classpath_scanner",
+        artifact = "io.github.lukehutch:fast-classpath-scanner:2.21",
+        sha1 = "0cc8e22b412521480c89ac79194e82bd4471dd75",
+    )
+
+    native.maven_jar(
         name = "jdom",
         artifact = "jdom:jdom:1.0",
         sha1 = "a2ac1cd690ab4c80defe7f9bce14d35934c35cec",
@@ -800,8 +860,8 @@
 
     native.maven_jar(
         name = "typesafe_config",
-        artifact = "com.typesafe:config:1.2.1",
-        sha1 = "f771f71fdae3df231bcd54d5ca2d57f0bf93f467",
+        artifact = "com.typesafe:config:1.3.2",
+        sha1 = "d6ac0ce079f114adce620f2360c92a70b2cb36dc",
     )
 
     native.maven_jar(
@@ -1185,6 +1245,48 @@
     )
 
     native.java_library(
+        name = "atomix_cluster",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_cluster//jar"],
+    )
+
+    native.java_library(
+        name = "atomix_gossip",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_gossip//jar"],
+    )
+
+    native.java_library(
+        name = "atomix_primary_backup",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_primary_backup//jar"],
+    )
+
+    native.java_library(
+        name = "atomix_primitive",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_primitive//jar"],
+    )
+
+    native.java_library(
+        name = "atomix_raft",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_raft//jar"],
+    )
+
+    native.java_library(
+        name = "atomix_storage",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_storage//jar"],
+    )
+
+    native.java_library(
+        name = "atomix_utils",
+        visibility = ["//visibility:public"],
+        exports = ["@atomix_utils//jar"],
+    )
+
+    native.java_library(
         name = "commons_codec",
         visibility = ["//visibility:public"],
         exports = ["@commons_codec//jar"],
@@ -1227,6 +1329,12 @@
     )
 
     native.java_library(
+        name = "fast_classpath_scanner",
+        visibility = ["//visibility:public"],
+        exports = ["@fast_classpath_scanner//jar"],
+    )
+
+    native.java_library(
         name = "jdom",
         visibility = ["//visibility:public"],
         exports = ["@jdom//jar"],
@@ -2190,7 +2298,14 @@
 artifact_map["@aopalliance_repackaged//jar"] = "mvn:org.glassfish.hk2.external:aopalliance-repackaged:jar:2.5.0-b42"
 artifact_map["@amqp_client//jar"] = "mvn:com.rabbitmq:amqp-client:jar:3.6.1"
 artifact_map["@asm//jar"] = "mvn:org.ow2.asm:asm:jar:5.0.4"
-artifact_map["@atomix//jar"] = "mvn:io.atomix:atomix:jar:2.0.23"
+artifact_map["@atomix//jar"] = "mvn:io.atomix:atomix:jar:3.0.0-rc5"
+artifact_map["@atomix_cluster//jar"] = "mvn:io.atomix:atomix-cluster:jar:3.0.0-rc5"
+artifact_map["@atomix_gossip//jar"] = "mvn:io.atomix:atomix-gossip:jar:3.0.0-rc5"
+artifact_map["@atomix_primary_backup//jar"] = "mvn:io.atomix:atomix-primary-backup:jar:3.0.0-rc5"
+artifact_map["@atomix_primitive//jar"] = "mvn:io.atomix:atomix-primitive:jar:3.0.0-rc5"
+artifact_map["@atomix_raft//jar"] = "mvn:io.atomix:atomix-raft:jar:3.0.0-rc5"
+artifact_map["@atomix_storage//jar"] = "mvn:io.atomix:atomix-storage:jar:3.0.0-rc5"
+artifact_map["@atomix_utils//jar"] = "mvn:io.atomix:atomix-utils:jar:3.0.0-rc5"
 artifact_map["@commons_codec//jar"] = "mvn:commons-codec:commons-codec:jar:1.10"
 artifact_map["@commons_cli//jar"] = "mvn:commons-cli:commons-cli:jar:1.3"
 artifact_map["@commons_collections//jar"] = "mvn:commons-collections:commons-collections:jar:3.2.2"
@@ -2198,6 +2313,7 @@
 artifact_map["@commons_io//jar"] = "mvn:commons-io:commons-io:jar:2.6"
 artifact_map["@commons_jxpath//jar"] = "mvn:commons-jxpath:commons-jxpath:jar:1.3"
 artifact_map["@commons_beanutils//jar"] = "mvn:commons-beanutils:commons-beanutils:jar:1.9.3"
+artifact_map["@fast_classpath_scanner//jar"] = "mvn:io.github.lukehutch:fast-classpath-scanner:jar:2.21"
 artifact_map["@jdom//jar"] = "mvn:jdom:jdom:jar:NON-OSGI:1.0"
 artifact_map["@commons_lang//jar"] = "mvn:commons-lang:commons-lang:jar:2.6"
 artifact_map["@commons_lang3//jar"] = "mvn:org.apache.commons:commons-lang3:jar:3.7"
@@ -2300,7 +2416,7 @@
 artifact_map["@slf4j_api//jar"] = "mvn:org.slf4j:slf4j-api:jar:1.7.25"
 artifact_map["@slf4j_jdk14//jar"] = "mvn:org.slf4j:slf4j-jdk14:jar:1.7.25"
 artifact_map["@slf4j_nop//jar"] = "mvn:org.slf4j:slf4j-nop:jar:1.7.25"
-artifact_map["@typesafe_config//jar"] = "mvn:com.typesafe:config:jar:1.2.1"
+artifact_map["@typesafe_config//jar"] = "mvn:com.typesafe:config:jar:1.3.2"
 artifact_map["@validation_api//jar"] = "mvn:javax.validation:validation-api:jar:1.1.0.Final"
 artifact_map["@checkstyle//jar"] = "mvn:com.puppycrawl.tools:checkstyle:jar:NON-OSGI:8.10"
 artifact_map["@apache_karaf//jar"] = "http://repo1.maven.org/maven2/org/onosproject/apache-karaf-offline/3.0.8/apache-karaf-offline-3.0.8.tar.gz"
diff --git a/tools/build/envDefaults b/tools/build/envDefaults
index 190f7de..d76bb52 100644
--- a/tools/build/envDefaults
+++ b/tools/build/envDefaults
@@ -53,6 +53,7 @@
 export ONOS_ADMIN_TAR=$ONOS_STAGE_ROOT/$ONOS_ADMIN_BITS.tar.gz
 
 export ONOS_INSTALL_DIR="/opt/onos"     # Installation directory on remote
+export ATOMIX_INSTALL_DIR="/opt/atomix" # Installation directory for Atomix
 export OCI="${OCI:-localhost}"          # ONOS Controller Instance
 export ONOS_USER="${ONOS_USER:-sdn}"    # ONOS user on remote system
 export ONOS_GROUP="${ONOS_GROUP:-sdn}"  # ONOS group on remote system
diff --git a/tools/package/features/BUCK b/tools/package/features/BUCK
index 479d488..8bbf94a 100644
--- a/tools/package/features/BUCK
+++ b/tools/package/features/BUCK
@@ -4,10 +4,18 @@
   required_features = [],
   included_bundles = [
     '//lib:atomix',
+    '//lib:atomix-cluster',
+    '//lib:atomix-gossip',
+    '//lib:atomix-primary-backup',
+    '//lib:atomix-primitive',
+    '//lib:atomix-raft',
+    '//lib:atomix-storage',
+    '//lib:atomix-utils',
     '//lib:commons-lang',
     '//lib:commons-lang3',
     '//lib:commons-text',
     '//lib:commons-configuration',
+    '//lib:fast-classpath-scanner',
     '//lib:guava',
     '//lib:netty',
     '//lib:netty-common',
diff --git a/tools/package/features/features.xml b/tools/package/features/features.xml
index a89555d..1c49602 100644
--- a/tools/package/features/features.xml
+++ b/tools/package/features/features.xml
@@ -21,7 +21,7 @@
     <feature name="onos-thirdparty-base" version="@FEATURE-VERSION"
              description="ONOS 3rd party dependencies">
         <bundle>mvn:commons-lang/commons-lang/2.6</bundle>
-        <bundle>mvn:org.apache.commons/commons-lang3/3.5</bundle>
+        <bundle>mvn:org.apache.commons/commons-lang3/3.7</bundle>
         <bundle>mvn:commons-configuration/commons-configuration/1.10</bundle>
         <bundle>mvn:com.google.guava/guava/22.0</bundle>
         <bundle>mvn:io.netty/netty/3.10.5.Final</bundle>
@@ -56,10 +56,20 @@
         <bundle>mvn:commons-configuration/commons-configuration/1.10</bundle>
         <bundle>mvn:commons-collections/commons-collections/3.2.2</bundle>
 
-        <bundle>mvn:com.typesafe/config/1.2.1</bundle>
+        <bundle>mvn:com.typesafe/config/1.3.2</bundle>
         <bundle>mvn:com.googlecode.concurrent-trees/concurrent-trees/2.6.0</bundle>
         <bundle>mvn:commons-io/commons-io/2.4</bundle>
-        <bundle>mvn:io.atomix/atomix/2.0.23</bundle>
+        <bundle>mvn:io.atomix/atomix/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-cluster/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-gossip/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-primary-backup/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-primitive/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-raft/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-storage/3.0.0-rc5</bundle>
+        <bundle>mvn:io.atomix/atomix-utils/3.0.0-rc5</bundle>
+
+        <bundle>mvn:com.typesafe/config/1.3.2</bundle>
+        <bundle>mvn:io.github.lukehutch/fast-classpath-scanner/2.21</bundle>
 
         <bundle>mvn:org.glassfish.jersey.core/jersey-client/2.26</bundle>
 
diff --git a/tools/package/onos-run-karaf b/tools/package/onos-run-karaf
index 1b53640..d35468a 100755
--- a/tools/package/onos-run-karaf
+++ b/tools/package/onos-run-karaf
@@ -55,8 +55,7 @@
     cat > $ONOS_DIR/config/cluster.json <<-EOF
     {
       "name": "default",
-      "nodes": [ {"id": "$IP", "ip": "$IP", "port": 9876 } ],
-      "partitions": [ { "id": 1, "members": [ "$IP" ] } ]
+      "cluster": [ {"id": "$IP", "ip": "$IP", "port": 9876 } ]
     }
 EOF
 
diff --git a/tools/test/bin/atomix-config b/tools/test/bin/atomix-config
new file mode 100755
index 0000000..0cc2954
--- /dev/null
+++ b/tools/test/bin/atomix-config
@@ -0,0 +1,41 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Remotely configures & starts Atomix for the first time.
+# -----------------------------------------------------------------------------
+
+function _usage () {
+cat << _EOF_
+usage:
+ $(basename $0) [node]
+
+options:
+- [node] : The node to configure
+
+summary:
+ Remotely configures and starts Atomix for the first time.
+
+ The procedure for configuring a node includes determining base features,
+ applications to load at startup, and clustering and logical network view
+ configurations, among others.
+
+ If [node] isn't specified, the default target becomes \$OCI.
+
+_EOF_
+}
+
+[ "$1" = "-h" ] && _usage && exit 0
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+node=${1:-$OCI}
+remote=$ONOS_USER@$node
+
+# Generate a default cluster.json from the ON* environment variables
+CDEF_FILE=/tmp/${remote}.atomix.json
+atomix-gen-config $node $CDEF_FILE
+scp -q $CDEF_FILE $remote:$ATOMIX_INSTALL_DIR/atomix.json
+
+ssh -tt $ONOS_USER@$node "
+    echo \"cd $ATOMIX_INSTALL_DIR && java -Xms4G -Xmx4G -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -cp .:$ATOMIX_INSTALL_DIR/atomix.json:$ATOMIX_INSTALL_DIR/atomix-agent.jar -Datomix.logging.level=DEBUG io.atomix.agent.AtomixAgent\" > $ATOMIX_INSTALL_DIR/atomix && sudo chmod u+x $ATOMIX_INSTALL_DIR/atomix
+"
diff --git a/tools/test/bin/atomix-gen-config b/tools/test/bin/atomix-gen-config
new file mode 100755
index 0000000..5e42dfc
--- /dev/null
+++ b/tools/test/bin/atomix-gen-config
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+"""
+usage: atomix-gen-config [-h] [-s PARTITION_SIZE] [-n NUM_PARTITIONS]
+                         [node_ip] [filename] [node_ip [node_ip ...]]
+
+Generate the partitions json file given a list of IPs or from the $OCC*
+environment variables.
+
+positional arguments:
+  filename              File to write output to. If none is provided, output
+                        is written to stdout.
+  node_ip               IP Address(es) of the node(s) in the cluster. If no
+                        IPs are given, will use the $OCC* environment
+                        variables. NOTE: these arguemnts are only processed
+                        after the filename argument.
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -s PARTITION_SIZE, --partition-size PARTITION_SIZE
+                        Number of nodes per partition. Note that partition
+                        sizes smaller than 3 are not fault tolerant. Defaults
+                        to 3.
+  -n NUM_PARTITIONS, --num-partitions NUM_PARTITIONS
+                        Number of partitions. Defaults to the number of nodes
+                        in the cluster.
+"""
+
+from os import environ
+import argparse
+import re
+import json
+
+convert = lambda text: int(text) if text.isdigit() else text.lower()
+alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
+
+def get_OCC_vars():
+  vars = []
+  for var in environ:
+    if re.match(r"OCC[0-9]+", var):
+      vars.append(var)
+  return sorted(vars, key=alphanum_key)
+
+def get_local_node(node, ips=None):
+    if not ips:
+        ips = [ environ[v] for v in get_OCC_vars() ]
+    return 'atomix-{}'.format(ips.index(node) + 1)
+
+def get_nodes(ips=None, default_port=5679):
+    node = lambda id, ip, port: {'id': id, 'address': '{}:{}'.format(ip, port)}
+    result = []
+    if not ips:
+        ips = [ environ[v] for v in get_OCC_vars() ]
+    i = 1
+    for ip_string in ips:
+        address_tuple = ip_string.split(":")
+        if len(address_tuple) == 3:
+            id=address_tuple[0]
+            ip=address_tuple[1]
+            port=int(address_tuple[2])
+        else:
+            id='atomix-{}'.format(i)
+            i += 1
+            ip=ip_string
+            port=default_port
+        result.append(node(id, ip, port))
+    return result
+
+def get_local_address(node, ips=None, default_port=5679):
+    result = []
+    if not ips:
+        ips = [ environ[v] for v in get_OCC_vars() ]
+    i = 1
+    for ip_string in ips:
+        address_tuple = ip_string.split(":")
+        if len(address_tuple) == 3:
+            id=address_tuple[0]
+            ip=address_tuple[1]
+            port=int(address_tuple[2])
+            if node == id or node == ip:
+                return '{}:{}'.format(ip, port)
+    return '{}:{}'.format(node, default_port)
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser(
+      description="Generate the partitions json file given a list of IPs or from the $OCC* environment variables.")
+  parser.add_argument(
+      '-s', '--partition-size', type=int, default=3,
+      help="Number of nodes per partition. Note that partition sizes smaller than 3 are not fault tolerant. Defaults to 3." )
+  parser.add_argument(
+      '-n', '--num-partitions', type=int,
+      help="Number of partitions. Defaults to the number of nodes in the cluster." )
+ # TODO: make filename and nodes independent. This will break backwards compatibility with existing usage.
+  parser.add_argument(
+      'node', metavar='node_ip', type=str, help='IP address of the node for which to generate the configuration')
+  parser.add_argument(
+     'filename', metavar='filename', type=str, nargs='?',
+     help='File to write output to. If none is provided, output is written to stdout.')
+  parser.add_argument(
+      'nodes', metavar='node_ip', type=str, nargs='*',
+      help='IP Address(es) of the node(s) in the cluster. If no IPs are given, ' +
+           'will use the $OCC* environment variables. NOTE: these arguemnts' +
+           ' are only processed after the filename argument.')
+
+  args = parser.parse_args()
+  filename = args.filename
+  partition_size = args.partition_size
+  local_member_id = get_local_node(args.node)
+  local_member_address = get_local_address(args.node, args.nodes)
+  nodes = get_nodes(args.nodes)
+  num_partitions = args.num_partitions
+  if not num_partitions:
+    num_partitions = len(nodes)
+
+  data = {
+      'cluster': {
+          'clusterId': 'onos',
+          'node': {
+              'id': local_member_id,
+              'address': local_member_address
+          },
+          'discovery': {
+              'type': 'bootstrap',
+              'nodes': nodes
+          }
+      },
+      'managementGroup': {
+          'type': 'raft',
+          'partitions': 1,
+          'partitionSize': len(nodes),
+          'members': [node['id'] for node in nodes]
+      },
+      'partitionGroups': {
+          'raft': {
+              'type': 'raft',
+              'partitions': num_partitions,
+              'partitionSize': partition_size,
+              'members': [node['id'] for node in nodes]
+          }
+      }
+  }
+  output = json.dumps(data, indent=4)
+
+  if filename:
+    with open(filename, 'w') as f:
+      f.write(output)
+  else:
+    print output
diff --git a/tools/test/bin/atomix-install b/tools/test/bin/atomix-install
new file mode 100755
index 0000000..2a361b1
--- /dev/null
+++ b/tools/test/bin/atomix-install
@@ -0,0 +1,60 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Remotely pushes bits to a remote node and installs ONOS on it.
+# -----------------------------------------------------------------------------
+
+function _usage () {
+cat << _EOF_
+usage:
+ $(basename $0) [-fn] [-m] <settings> [node]
+
+options:
+- [node] : remote node to install ONOS on.
+
+summary:
+ Downloads Atomix bits to a remote node and installs Atomix on it.
+
+ The -u should be used on upstart-based systems.
+
+ If [node] is not specified the default target is \$OCI.
+
+_EOF_
+}
+
+[ "$1" = "-h" ] && _usage && exit 0
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+onos-check-bits
+
+while getopts fnvm: o; do
+    case "$o" in
+        f) uninstall=true;;
+        n) nostart=true;;
+    esac
+done
+let OPC=$OPTIND-1
+shift $OPC
+
+# If the -f was given, attempt uninstall first.
+[ -n "$uninstall" ] && atomix-uninstall ${1:-$OCI}
+
+node=${1:-$OCI}
+remote=$ONOS_USER@$node
+
+ssh -tt $remote "
+    [ -f $ATOMIX_INSTALL_DIR/atomix-agent.jar ] && echo \"Atomix is already installed\" && exit 1
+
+    sudo mkdir -p $ATOMIX_INSTALL_DIR && sudo chown ${ONOS_USER}:${ONOS_GROUP} $ATOMIX_INSTALL_DIR
+    sudo wget -O $ATOMIX_INSTALL_DIR/atomix-agent.jar https://oss.sonatype.org/content/repositories/releases/io/atomix/atomix-agent/3.0.0-rc4/atomix-agent-3.0.0-rc4-shaded.jar
+"
+
+# Configure the ONOS installation
+atomix-config $node
+
+# Upload the shared cluster key if present
+[ -f "$ONOS_CLUSTER_KEY_FILE" ] && onos-push-cluster-key $1
+
+# Unless -n option was given, attempt to ignite the ONOS service.
+[ -z "$nostart" ] && atomix-service $node start || true
\ No newline at end of file
diff --git a/tools/test/bin/atomix-kill b/tools/test/bin/atomix-kill
new file mode 100755
index 0000000..8238fd3
--- /dev/null
+++ b/tools/test/bin/atomix-kill
@@ -0,0 +1,18 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Remotely kills the Atomix service on the specified node.
+# -----------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+ssh $ONOS_USER@${1:-$OCI} "
+    pid=\$(ps -ef | grep atomix-agent.jar | grep -v grep | cut -c10-15 | tr -d ' ')
+    if [ -n \"\$pid\" ]; then
+        echo \"Killing Atomix process \$pid on \$(hostname)...\"
+        kill -9 \$pid
+    else
+        echo \"Atomix process is not running...\"
+        exit 1
+    fi
+"
diff --git a/tools/test/bin/atomix-service b/tools/test/bin/atomix-service
new file mode 100755
index 0000000..0c70b0f
--- /dev/null
+++ b/tools/test/bin/atomix-service
@@ -0,0 +1,69 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Remotely administers the ONOS service on the specified node.
+# -----------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+. $ONOS_ROOT/tools/test/bin/find-node.sh
+
+function print_usage {
+    command_name=`basename $0`
+    echo "Remotely administer the ONOS service on a single node or the current ONOS cell."
+    echo
+    echo "Usage:     $command_name <TARGET> [COMMAND]"
+    echo "           $command_name [-h | --help]"
+    echo "Options:"
+    echo "    TARGET          The target of the command"
+    echo "    COMMAND         The command to execute. Default value is 'status'"
+    echo "    [-h | --help]   Print this help"
+    echo ""
+    echo "TARGET:  <hostname | --cell>"
+    echo "      hostname        Execute on the specified host name"
+    echo "        --cell        Execute on the current ONOS cell"
+    echo ""
+    echo "COMMAND: [start|stop|restart|status]"
+    echo ""
+}
+
+# Print usage
+if [ "${1}" = "-h" -o "${1}" = "--help" ]; then
+    print_usage
+    exit 0
+fi
+
+# Select the target
+if [ "${1}" = "--cell" ]; then
+    nodes=$(env | sort | egrep "^OCC[0-9]+" | cut -d= -f2)
+else
+    nodes=$(find_node ${1:-$OCI})
+fi
+
+case $2 in
+    start)
+        # Execute the remote commands
+        for node in $nodes; do
+            ssh $ONOS_USER@${node} "nohup $ATOMIX_INSTALL_DIR/atomix >> $ATOMIX_INSTALL_DIR/log 2>&1 &"
+        done
+    ;;
+    stop)
+        # Execute the remote commands
+        for node in $nodes; do
+            ssh -tt $ONOS_USER@${node} "
+                pid=\$(ps -ef | grep atomix-agent.jar | grep -v grep | cut -c10-15 | tr -d ' ')
+                if [ -n \"\$pid\" ]; then
+                    echo \"Killing Atomix process \$pid on \$(hostname)...\"
+                    kill -9 \$pid
+                else
+                    echo \"Atomix process is not running...\"
+                    exit 1
+                fi
+            "
+        done
+    ;;
+    *)
+        echo "error: $2 is not a valid command"
+        echo ""
+        print_usage
+    ;;
+esac
diff --git a/tools/test/bin/atomix-uninstall b/tools/test/bin/atomix-uninstall
new file mode 100755
index 0000000..c1ca86d
--- /dev/null
+++ b/tools/test/bin/atomix-uninstall
@@ -0,0 +1,42 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Remotely stops & uninstalls ONOS on the specified node.
+# -----------------------------------------------------------------------------
+
+function _usage () {
+cat << _EOF_
+usage:
+ $(basename $0) [node]
+
+options:
+- [node] : The remote instance to uninstall Atomix from.
+
+summary:
+ Remotely stops and uninstalls Atomix on the specified node.
+
+ If [node] isn't specified, \$OCI becomes the target.
+
+_EOF_
+}
+
+[ "$1" = "-h" ] && _usage && exit 0
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+remote=$ONOS_USER@${1:-$OCI}
+
+ssh -tt $remote "
+    pid=\$(ps -ef | grep atomix-agent.jar | grep -v grep | cut -c10-15 | tr -d ' ')
+    if [ -n \"\$pid\" ]; then
+        echo \"Killing Atomix process \$pid on \$(hostname)...\"
+        kill -9 \$pid
+    else
+        echo \"Atomix process is not running...\"
+    fi
+
+    # Remove Atomix directory
+    [ -d $ATOMIX_INSTALL_DIR ] && sudo rm -fr $ATOMIX_INSTALL_DIR
+
+    exit \${status:-0};
+"
diff --git a/tools/test/bin/onos-config b/tools/test/bin/onos-config
index 97519b8..772df3a 100755
--- a/tools/test/bin/onos-config
+++ b/tools/test/bin/onos-config
@@ -43,7 +43,9 @@
         >> $ONOS_INSTALL_DIR/$KARAF_DIST/etc/system.properties
 
     # Drop copycat related log level for the console
-    echo "log4j.logger.net.kuujo.copycat= INFO" \
+    echo "log4j.logger.io.atomix=INFO" \
+        >> $ONOS_INSTALL_DIR/$KARAF_DIST/etc/org.ops4j.pax.logging.cfg
+    echo "log4j.logger.io.atomix.cluster.messaging=ERROR" \
         >> $ONOS_INSTALL_DIR/$KARAF_DIST/etc/org.ops4j.pax.logging.cfg
 
     # Patch the Apache Karaf distribution file to load ONOS boot features
@@ -64,7 +66,7 @@
 
 # Generate a default cluster.json from the ON* environment variables
 CDEF_FILE=/tmp/${remote}.cluster.json
-onos-gen-partitions $CDEF_FILE
+onos-gen-config $CDEF_FILE
 scp -q $CDEF_FILE $remote_scp:$ONOS_INSTALL_DIR/config/cluster.json
 
 # Copy tools/package/config/ to remote
diff --git a/tools/test/bin/onos-gen-partitions b/tools/test/bin/onos-gen-config
similarity index 74%
rename from tools/test/bin/onos-gen-partitions
rename to tools/test/bin/onos-gen-config
index a982c36..ef8f2f0 100755
--- a/tools/test/bin/onos-gen-partitions
+++ b/tools/test/bin/onos-gen-config
@@ -1,9 +1,9 @@
 #!/usr/bin/env python
 """
-usage: onos-gen-partitions [-h] [-s PARTITION_SIZE] [-n NUM_PARTITIONS]
-                           [filename] [node_ip [node_ip ...]]
+usage: onos-gen-config [-h] [-s PARTITION_SIZE] [-n NUM_PARTITIONS]
+                            [filename] [node_ip [node_ip ...]]
 
-Generate the partitions json file given a list of IPs or from the $OC*
+Generate the partitions json file given a list of IPs or from the $OCC*
 environment variables.
 
 positional arguments:
@@ -11,7 +11,7 @@
                         is written to stdout.
   node_ip               IP Address(es) of the node(s) in the cluster. If no
                         IPs are given, will use the $OC* environment
-                        variables. NOTE: these arguemnts are only processed
+                        variables. NOTE: these arguments are only processed
                         after the filename argument.
 
 optional arguments:
@@ -26,27 +26,26 @@
 """
 
 from os import environ
-from collections import deque
 import argparse
 import re
 import json
-import hashlib
 
 convert = lambda text: int(text) if text.isdigit() else text.lower()
 alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
 
-def get_OC_vars():
+def get_OCC_vars():
   vars = []
   for var in environ:
-    if re.match(r"OC[0-9]+", var):
+    if re.match(r"OCC[0-9]+", var):
       vars.append(var)
   return sorted(vars, key=alphanum_key)
 
-def get_nodes(ips=None, default_port=9876):
+def get_nodes(ips=None, default_port=5679):
     node = lambda id, ip, port : { 'id': id, 'ip': ip, 'port': port }
     result = []
     if not ips:
-        ips = [ environ[v] for v in get_OC_vars() ]
+        ips = [ environ[v] for v in get_OCC_vars() ]
+    i = 1
     for ip_string in ips:
         address_tuple = ip_string.split(":")
         if len(address_tuple) == 3:
@@ -54,24 +53,13 @@
             ip=address_tuple[1]
             port=int(address_tuple[2])
         else:
-            id=ip_string
+            id='atomix-{}'.format(i)
+            i += 1
             ip=ip_string
             port=default_port
         result.append(node(id, ip, port))
     return result
 
-def generate_partitions(nodes, k, n):
-  l = deque(nodes)
-  perms = []
-  for i in range(1, n+1):
-    part = {
-             'id': i,
-             'members': list(l)[:k]
-           }
-    perms.append(part)
-    l.rotate(-1)
-  return perms
-
 if __name__ == '__main__':
   parser = argparse.ArgumentParser(
       description="Generate the partitions json file given a list of IPs or from the $OC* environment variables.")
@@ -94,20 +82,14 @@
   args = parser.parse_args()
   filename = args.filename
   partition_size = args.partition_size
-  nodes = get_nodes(args.nodes)
+  cluster = get_nodes(args.nodes)
   num_partitions = args.num_partitions
   if not num_partitions:
-    num_partitions = len(nodes)
+    num_partitions = len(cluster)
 
-  partitions = generate_partitions([v.get('id') for v in nodes], partition_size, num_partitions)
-  m = hashlib.sha256()
-  for node in nodes:
-    m.update(node['ip'])
-  name = int(m.hexdigest()[:8], base=16) # 32-bit int based on SHA256 digest
   data = {
-           'name': name,
-           'nodes': nodes,
-           'partitions': partitions
+           'name': 'onos',
+           'cluster': cluster
          }
   output = json.dumps(data, indent=4)
 
diff --git a/tools/test/scenarios/setup.xml b/tools/test/scenarios/setup.xml
index e87996d..a3bdf32 100644
--- a/tools/test/scenarios/setup.xml
+++ b/tools/test/scenarios/setup.xml
@@ -27,33 +27,50 @@
                 <step name="Kill-${#}" env="~" exec="onos-kill ${OC#}"
                       requires="Uninstall-${#}"/>
             </parallel>
+            <parallel var="${OCC#}">
+                <step name="Atomix-Kill-${#}"
+                      env="~"
+                      exec="atomix-kill ${OCC#}"/>
+                <step name="Atomix-Uninstall-${#}"
+                      exec="atomix-uninstall ${OCC#}"
+                      requires="Atomix-Kill-${#}"/>
+            </parallel>
         </group>
 
-        <group name="Install">
+        <group name="Install-Atomix">
             <step name="Generate-Cluster-Key" exec="onos-gen-cluster-key -f" />
 
-            <group name="Sequential-Install" if="${ONOS_STC_SEQ_START}">
-                <sequential var="${OC#}"
-                            starts="Sequential-Install-${#}"
-                            ends="Sequential-Install-${#-1}">
-                    <step name="Sequential-Install-${#}" exec="onos-install ${OC#}"
-                          requires="Generate-Cluster-Key,Push-Bits-${#},Push-Bits,Cleanup"/>
-                </sequential>
-            </group>
-
-            <group name="Parallel-Install" unless="${ONOS_STC_SEQ_START}">
-                <parallel var="${OC#}">
-                    <step name="Parallel-Install-${#}" exec="onos-install ${OC#}"
+            <group name="Parallel-Install-Atomix">
+                <parallel var="${OCC#}">
+                    <step name="Parallel-Install-Atomix-${#}" exec="atomix-install ${OCC#}"
                           requires="Generate-Cluster-Key,Push-Bits-${#},Push-Bits,Cleanup"/>
                 </parallel>
             </group>
         </group>
 
-        <group name="Verify" requires="Install">
+        <group name="Install-ONOS">
+            <group name="Sequential-Install-ONOS" if="${ONOS_STC_SEQ_START}">
+                <sequential var="${OC#}"
+                            starts="Sequential-Install-${#}"
+                            ends="Sequential-Install-${#-1}">
+                    <step name="Sequential-Install-${#}" exec="onos-install ${OC#}"
+                          requires="Generate-Cluster-Key,Push-Bits-${#},Push-Bits,Cleanup,Install-Atomix"/>
+                </sequential>
+            </group>
+
+            <group name="Parallel-Install-ONOS" unless="${ONOS_STC_SEQ_START}">
+                <parallel var="${OC#}">
+                    <step name="Parallel-Install-${#}" exec="onos-install ${OC#}"
+                          requires="Generate-Cluster-Key,Push-Bits-${#},Push-Bits,Cleanup,Install-Atomix"/>
+                </parallel>
+            </group>
+        </group>
+
+        <group name="Verify" requires="Install-ONOS">
             <parallel var="${OC#}">
                 <step name="Secure-SSH-${#}"
                       exec="onos-secure-ssh -u ${ONOS_WEB_USER} -p ${ONOS_WEB_PASS} ${OC#}"
-                      requires="Install"/>
+                      requires="Install-ONOS"/>
 
                 <step name="Wait-for-Start-${#}" exec="onos-wait-for-start ${OC#}"
                       requires="~Secure-SSH-${#}"/>
diff --git a/tools/test/scenarios/teardown.xml b/tools/test/scenarios/teardown.xml
index 3199d25..3d2179e 100644
--- a/tools/test/scenarios/teardown.xml
+++ b/tools/test/scenarios/teardown.xml
@@ -16,7 +16,10 @@
 <scenario name="teardown" description="ONOS cluster teardown/uninstall">
     <group name="Teardown">
         <parallel var="${OC#}">
-            <step name="Uninstall-${#}" exec="onos-uninstall ${OC#}"/>
+            <step name="ONOS-Uninstall-${#}" exec="onos-uninstall ${OC#}"/>
+        </parallel>
+        <parallel var="${OCC#}">
+            <step name="Atomix-Uninstall-${#}" exec="atomix-uninstall ${OCC#}"/>
         </parallel>
     </group>
 </scenario>