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)