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/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)