Initial cut of `onos-group` script for running a subset of commands
against multiple ONOS instances:

  - onos-install
  - onos-push-keys
  - onos-kill
  - onos-patch-vm
  - onos-uninstall

onos-group is simply a wrapper that does minimal sanity checks. New
commands are added by adding them to the GOPTS list in ogroup-opts.

Reference: ONOS-536

Change-Id: Ib3055491fec80e8759e87594e81a88285546deaf
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 79717e3..9bec1aa 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -146,11 +146,13 @@
        printf "${cdf} : no such cell\n" && return 1
   fi
 
-  if [ -z "$EDITOR" ]; then
-    vi ${cpath}${cdf}
+  if [ -z "${EDITOR}" ] || [ -x "$(which ${EDITOR})" ]; then
+    unset EDITOR && vi ${cpath}${cdf}
   else
     $EDITOR ${cpath}${cdf}
   fi
   ($apply) && cell ${cdf}
-
 }
+
+# autocomplete for certain utilities
+. ${ONOS_ROOT}/tools/test/bin/ogroup-opts
diff --git a/tools/test/bin/ogroup-opts b/tools/test/bin/ogroup-opts
new file mode 100644
index 0000000..e9b030c
--- /dev/null
+++ b/tools/test/bin/ogroup-opts
@@ -0,0 +1,14 @@
+# tab completion settings for onos-group.
+
+# options available to onos-group
+GOPTS='install kill patch-vm push-keys uninstall'
+
+function _ogroup-opts () {
+  local cur=${COMP_WORDS[COMP_CWORD]}
+
+  if [ $COMP_CWORD -eq 1 ]; then
+    COMPREPLY=( $( compgen -W "$GOPTS help" -- $cur ) )
+  fi
+}
+
+complete -F _ogroup-opts onos-group
diff --git a/tools/test/bin/onos-group b/tools/test/bin/onos-group
new file mode 100755
index 0000000..0e6b396
--- /dev/null
+++ b/tools/test/bin/onos-group
@@ -0,0 +1,90 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Allows a select group of commands to be sent to all ONOS instances in a cell.
+# -----------------------------------------------------------------------------
+
+set -o pipefail
+IFS=$'\n'
+
+source ogroup-opts
+
+function err() {
+    printf '%s: %s: %s\n' "$(basename $0)" "$1" "$2" >&2
+    usage >&2
+    exit 1
+}
+
+function usage() {
+cat << EOF
+
+usage: $(basename $0) <help|[command]>
+
+Sends a command to all ONOS instances in the current cell. Currently supported
+commands are: $GOPTS
+
+options:
+    [command]  : A command to send to the instances.
+    help       : Displays this message and exits.
+
+notes:
+    Hitting <TAB> will display the options for $(basename $0).
+
+EOF
+}
+
+# gets the utility name
+function getcmd() {
+  # check that utility can be run in "batch-mode"
+  local isgopt=false
+  for c in $(printf '%s' "$GOPTS" | tr ' ' $'\n'); do
+    [ "$c" = "$1" ] && isgopt=true && break
+  done
+  if $isgopt ; then
+    printf 'onos-%s' "$1"
+  else
+    err 'unsupported command' "$1"
+  fi
+}
+
+# early sanity check for instances/arguments
+[ -z "$1" ] && usage && exit 0
+
+OCIS=( $(env | sed -ne 's:OC[0-9]=\(.*\):\1 :g p' | sort -k1) )
+if [ -z "$OCIS" ]; then
+  printf "no controller instances, quitting early" >&2 && exit 0
+fi
+
+CMD_HELP=false
+while [ $# -gt 0 ]; do
+  case "$1" in
+    'help')
+      usage && exit 0
+      ;;
+    '-'?)
+      err 'invalid flag' "$1" && exit 1
+      ;;
+     *)
+      cmd=$(getcmd $1) || exit 1
+      shift
+      # grab flags aimed at the utility being called.
+      argv=( $@ )
+      args=()
+      for i in "${!argv[@]}"; do
+        # 'help' is a parameter for us; '-h' is for the command
+        [ "${argv[$i]}" = 'help' ] && break
+        [ "${argv[$i]}" = '-h' ] && CMD_HELP=true
+        args[$i]="${argv[$i]}"
+        shift
+      done
+      continue
+      ;;
+  esac
+  shift
+done
+
+( $CMD_HELP ) && $cmd '-h' && exit 0 
+
+# TODO: verbose-mode and cleanup
+for i in ${OCIS[@]}; do
+  ${cmd} $(echo ${args[@]}) "$i" 2>/dev/null &
+done