This is an automated email from the ASF dual-hosted git repository.

ctubbsii pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/2.1 by this push:
     new 9e146f95a4 Backport and improve the cluster scripts to 2.1 (#5174)
9e146f95a4 is described below

commit 9e146f95a455e3d760fd50bc468373ffc5b3b021
Author: Christopher Tubbs <ctubb...@apache.org>
AuthorDate: Sat Dec 21 20:58:22 2024 -0500

    Backport and improve the cluster scripts to 2.1 (#5174)
    
    This backports the improvements to the accumulo-cluster and
    accumulo-service scripts to 2.1 to improve script maintenance and
    functionality across the branches. It also adds additional improvements
    to merge up to the newer branches for 3.1.0 and 4.0.0.
    
    Notable differences with the main branch:
    
    * This version includes deprecated commands that are replaced by newer
      versions: start-non-tservers, start-servers, stop-servers,
      start-tservers, and stop-tservers
    * This version includes the ability to control starting a
      compaction-coordinator, which was removed in the main branch for 4.0
    * This version does not allow specifying tserver groups (unsupported by
      its cluster.yaml version) and uses a hard-coded value of "default"
    * As before these changes, this version checks for old deprecated config
      files present in conf/
    * As before these changes, this version parses the cluster.yaml that was
      supported for the 2.1 and 3.1 branches
    * This version is modified to translate the variables emitted from its
      version of the ClusterConfigParser to the newer names used in the main
      branch
    * This version does not require compactor groups to be configured (since
      external compactions are optional)
    * This version uses `-a <host>` instead of `-o
      general.process.bind.address=<host>` for the bind address
    * This version uses `-q` and `-g` for the group parameter
    
    This includes relevant changes from #4966 to standardize the service
    names, #5066 to reduce ssh connections to hosts, #5114 to support
    starting/stopping groups of servers by type, #5126 to support resource
    group overrides, #5116 to refactor and simplify the script argument
    parsing using getopt, and the bugfixes and improvements from #5167
    
    Additional improvements:
    
    * Give the temporary file a better name, in case it doesn't get
      cleaned up, it will be easier to figure out what created it
    * Fix bug with invalid input that stopped processing options
      after detecting one that was invalid, and failed to honor the
      remaining good options, leading to serious issues affecting
      a larger set of servers than intended; this was fixed by checking
      the exit code from getopt
    * Also fixes an issue where `stop-here` would communicate with the
      accumulo manager and gracefully stop all tservers.
    * Make sure we have the correct getopt tool (and not the older getopt)
    * Verify the number of non-option parameters is exactly 1 (the command
      to execute)
    * Quote the variable containing the accumulo command since it could
      contain spaces
    * Remove --all (hasn't been included in any release yet, so that's fine)
      When I was updating the usage, I realized I couldn't write up a
      description of this that demonstrated that it made sense to exist;
      it can only be used when none of the other service selection options
      are used, but if none of the other service selection options are used,
      then that's already the behavior, so the flag does nothing other than
      introduce an error when it's misused; if we add it to be required,
      then we introduce an error when no options are used, but that's the
      most readable and intuitive case, so that would be weird; overall, it
      seemed best to remove the flag; this simplifies the implementation
      slightly, where we assume all service types, until that assumption is
      corrected by the use of any other service type flag
    * add more debugging output for original arguments vs. the parsed ones
    * This fixes #5196 by moving the parsing of the command into the
      `parse_args` method after getopt and strictly verifying that only one
      non-option argument exists (also relaxes the constraint that it has to
      be the first argument to the script; now it can be anywhere)
    * add a quote function that uses getopt to do the quoting; as explained
      in the inline comments, it produces more readable quoted output
    * standardize function definition syntax (keeps both the optional
      keyword "function" and the parens that become optional when the
      keyword is used)
    * Fix some grammar/spelling of script messages (fixes the typo of
      "stoping tablet servers", because "stop" command requires a second "p"
      when putting "ing" on the end)
    * parse args before doing anything else in the script, since parsing
      args doesn't depend on anything else, so if there's a problem parsing,
      that should be noticed before doing anything else
    * Add colors and improve messages
    * Fix description and behavior of using server options multiple times
      (it might be better to restrict being able to call it multiple times)
    
    ---------
    
    Co-authored-by: Daniel Roberts <ddani...@gmail.com>
---
 assemble/bin/accumulo-cluster                      | 1140 ++++++++++----------
 assemble/bin/accumulo-service                      |  112 +-
 assemble/conf/accumulo-env.sh                      |   32 +-
 assemble/conf/accumulo.properties                  |    5 +
 .../core/conf/cluster/ClusterConfigParser.java     |    7 +-
 5 files changed, 674 insertions(+), 622 deletions(-)

diff --git a/assemble/bin/accumulo-cluster b/assemble/bin/accumulo-cluster
index 130f7adf46..ab39fcec62 100755
--- a/assemble/bin/accumulo-cluster
+++ b/assemble/bin/accumulo-cluster
@@ -18,648 +18,649 @@
 # under the License.
 #
 
-function print_usage {
+#
+# Environment variables that can be set to influence the behavior
+# of this script
+#
+# ACCUMULO_LOCALHOST_ADDRESSES - set to a space delimited string of localhost 
names
+#                                and addresses to override the default lookups
+#
+
+function print_usage() {
   cat <<EOF
-Usage: accumulo-cluster <command> (<argument> ...) [<option> ...]
-
-Options:
-  --dry-run                  Prints information and commands, but does not 
execute them
-
-Commands:
-  create-config              Creates cluster config
-  restart                    Restarts the Accumulo cluster
-  start                      Starts Accumulo cluster
-  stop                       Stops Accumulo cluster
-  kill                       Kills Accumulo cluster
-  start-non-tservers         Deprecated. Starts all services except tservers
-  start-servers [--all|--tservers|--no-tservers|--sservers 
[group]|--compactors [queue]]
-                             Starts various server types, can optionally 
specify a queue or group
-  stop-servers [--all|--tservers| --no-tservers|--sservers 
[group]|--compactors [queue]]
-                             Starts various server types, can optionally 
specify a queue or group
-  start-tservers             Deprecated. Starts all tservers on cluster
-  stop-tservers              Deprecated. Stops all tservers on cluster
-  start-here                 Starts all services on this node
-  stop-here                  Stops all services on this node
+$(cyan Usage): $(green accumulo-cluster) <$(yellow command)> [<$(yellow 
option)> ...]
+
+$(cyan Options):
+  $(cyan General options):
+    $(green --dry-run)                Prints information and commands, but 
does not execute them
+    $(green --local)                  Operates on matching local services only 
(no SSH)
+
+  $(cyan 'Service type selection options (if none are used, all service types 
are selected)'):
+    $(green --manager)                Selects the manager service (oversees 
cluster operations)
+    $(green --gc)                     Selects the gc service (cleans up unused 
files)
+    $(green --monitor)                Selects the monitor web service (shows 
cluster information)
+    $(green --compaction-coordinator) Selects the compaction-coordinator 
service (external compactions)
+    $(green --tservers)               Selects the tablet server services 
(read/write operations on tablets)
+    $(green --sservers)[$(yellow '=group')]       Selects the scan server 
services (read-only eventually consistent scans)
+    $(green --compactors)[$(yellow '=group')]     Selects the compactor 
services (external compactions)
+    $(green --no-tservers)            $(yellow Deprecated). Selects service 
types except tservers (for backwards compatibility)
+    $(yellow NOTE): some server types support an optional $(yellow group) 
name. If it is not provided or is empty, then
+          all groups are considered. Declare multiple groups as a single 
space-separated parameter. Later
+          options overwrite earlier ones, so $(yellow '--sservers="g1 g2" 
--sserver') operates on all sserver groups.
+
+$(cyan Commands):
+  $(green create-config)              Creates cluster config (ignores service 
selection options)
+  $(green start)                      Starts Accumulo cluster services
+  $(green stop)                       Stops Accumulo cluster services
+  $(green restart)                    Restarts Accumulo cluster services
+  $(green kill)                       Kills Accumulo cluster services
+
+  $(cyan Deprecated commands):
+    $(green start-non-tservers)       $(yellow Deprecated). Alias for "start 
--no-tservers"
+    $(green start-servers)            $(yellow Deprecated). Alias for "start"
+    $(green stop-servers)             $(yellow Deprecated). Alias for "stop"
+    $(green start-tservers)           $(yellow Deprecated). Alias for "start 
--tservers"
+    $(green stop-tservers)            $(yellow Deprecated). Alias for "stop 
--tservers"
+    $(green start-here)               $(yellow Deprecated). Alias for "start 
--local"
+    $(green stop-here)                $(yellow Deprecated). Alias for "stop 
--local"
+
+$(cyan Examples):
+  $(purple 'accumulo-cluster start')                              $(blue '# 
start all servers')
+  $(purple 'accumulo-cluster start --dry-run')                    $(blue '# 
print debug information and commands to be executed')
+  $(purple 'accumulo-cluster start --local')                      $(blue '# 
start all local services')
+  $(purple 'accumulo-cluster start --local --manager')            $(blue '# 
start local manager services')
+  $(purple 'accumulo-cluster start --tservers')                   $(blue '# 
start all tservers')
+  $(purple 'accumulo-cluster start --sservers=group1')            $(blue '# 
start all group1 sservers')
+  $(purple 'accumulo-cluster start --sservers="group1 group2"')   $(blue '# 
start all group1 and group2 sservers')
+  $(purple 'accumulo-cluster start --local --manager --tservers') $(blue '# 
Start the local manager and local tservers')
+
 EOF
 }
 
-function invalid_args {
-  echo -e "Invalid arguments: $1\n"
+function checkTerminalSupportsColor() {
+  local c
+  # get the number of colors supported
+  c=$(tput colors 2>/dev/null) || c=-1
+  # if STDOUT is a terminal and the number of colors is at least 8
+  [[ -t 1 && $c -ge 8 ]]
+}
+
+function decolorize() {
+  # this only decolorizes escape sequences that we've added
+  # namely, the color codes 0;31m through 0;37m and the 0m reset
+  # it also can't deal with arrays, so can only work on a single param
+  if [[ $# -ne 1 ]]; then
+    echo "$(red ERROR): Couldn't decolorize multiple items: $*"
+  fi
+  local myvar=$1
+  myvar="${myvar//[[:cntrl:]]\[0;3[1-7]m/}" # remove the color codes
+  myvar="${myvar//[[:cntrl:]]\[0m/}"        # remove the reset code
+  echo "$myvar"
+}
+function colorize() {
+  local c=$1
+  shift
+  [[ $COLOR_ENABLED == 1 ]] && echo -e "\\e[0;${c}m${*}\\e[0m" || echo "$@"
+}
+function red() { colorize 31 "$@"; }
+function green() { colorize 32 "$@"; }
+function yellow() { colorize 33 "$@"; }
+function blue() { colorize 34 "$@"; }
+function purple() { colorize 35 "$@"; }
+function cyan() { colorize 36 "$@"; }
+function white() { colorize 37 "$@"; }
+
+function parse_args() {
+  local originalArgs=("$@")
+
+  DEBUG=0
+  ARG_LOCAL=0
+  ARG_ALL=1
+  ARG_MANAGER=0
+  ARG_GC=0
+  ARG_MONITOR=0
+  ARG_COORDINATOR=0
+  ARG_TSERVER=0
+  ARG_TSERVER_GROUP=""
+  ARG_SSERVER=0
+  ARG_SSERVER_GROUP=""
+  ARG_COMPACTOR=0
+  ARG_COMPACTOR_GROUP=""
+
+  # the correct version of getopt will always exit with exit code 4 when 
provided the `-T` flag
+  getopt -T &>/dev/null
+  if (($? != 4)); then
+    echo "$(red ERROR): Missing $(yellow util-linux) (or equivalent) version 
of $(green getopt). Unable to continue."
+    exit 2
+  fi
+
+  if ! PARSE_OUTPUT=$(getopt -o "" --long 
"dry-run,local,manager,gc,monitor,compaction-coordinator,no-tservers,tservers,sservers::,compactors::"
 -n 'accumulo-cluster' -- "$@"); then
+    print_usage
+    exit 1
+  fi
+
+  eval set -- "$PARSE_OUTPUT"
+
+  while true; do
+    case "$1" in
+      --dry-run)
+        DEBUG=1
+        debug "original args: $(quote "${originalArgs[@]}")"
+        debug "parsed args: $PARSE_OUTPUT"
+        shift 1
+        ;;
+      --local)
+        ARG_LOCAL=1
+        shift 1
+        ;;
+      --manager)
+        ARG_ALL=0
+        ARG_MANAGER=1
+        shift 1
+        ;;
+      --gc)
+        ARG_ALL=0
+        ARG_GC=1
+        shift 1
+        ;;
+      --monitor)
+        ARG_ALL=0
+        ARG_MONITOR=1
+        shift 1
+        ;;
+      --compaction-coordinator)
+        ARG_ALL=0
+        ARG_COORDINATOR=1
+        shift 1
+        ;;
+      --tservers)
+        ARG_ALL=0
+        ARG_TSERVER=1
+        shift 1
+        ;;
+      --sservers)
+        ARG_ALL=0
+        ARG_SSERVER=1
+        ARG_SSERVER_GROUP=$2
+        shift 2
+        ;;
+      --compactors)
+        ARG_ALL=0
+        ARG_COMPACTOR=1
+        ARG_COMPACTOR_GROUP=$2
+        shift 2
+        ;;
+      --no-tservers)
+        echo "$(yellow WARN): The $(yellow "--no-tservers") option is 
deprecated. Please specify the servers you wish to manage instead"
+        ARG_ALL=0
+        ARG_MANAGER=1
+        ARG_GC=1
+        ARG_MONITOR=1
+        ARG_COORDINATOR=1
+        ARG_SSERVER=1
+        ARG_COMPACTOR=1
+        shift 1
+        ;;
+      --)
+        shift
+        break
+        ;;
+      *)
+        echo "$(red ERROR): Unhandled option: $(yellow "$1")"
+        print_usage
+        exit 1
+        ;;
+    esac
+  done
+
+  if [[ $# -eq 0 ]]; then
+    invalid_args "<command> cannot be empty"
+  elif [[ $# -ne 1 ]]; then
+    # use getopt to display the remaining non-option parameters quoted for 
readability
+    local remaining
+    remaining="$(getopt -o "" -- "$@")"
+    invalid_args "Only one <$(yellow command)> is allowed, but found:$(yellow 
"${remaining#*--}")"
+  fi
+  ARG_CMD=$1
+  debug "ARG_CMD=$ARG_CMD"
+  debug "ARG_ALL=$ARG_ALL"
+  debug "ARG_LOCAL=$ARG_LOCAL"
+  debug "ARG_MANAGER=$ARG_MANAGER"
+  debug "ARG_GC=$ARG_GC"
+  debug "ARG_MONITOR=$ARG_MONITOR"
+  debug "ARG_COORDINATOR=$ARG_COORDINATOR"
+  debug "ARG_TSERVER=$ARG_TSERVER"
+  debug "ARG_TSERVER_GROUP=$ARG_TSERVER_GROUP"
+  debug "ARG_SSERVER=$ARG_SSERVER"
+  debug "ARG_SSERVER_GROUP=$ARG_SSERVER_GROUP"
+  debug "ARG_COMPACTOR=$ARG_COMPACTOR"
+  debug "ARG_COMPACTOR_GROUP=$ARG_COMPACTOR_GROUP"
+
+}
+
+function invalid_args() {
+  echo "$(red ERROR): $(yellow invalid arguments): $*"
+  echo
   print_usage 1>&2
   exit 1
 }
 
-function parse_fail {
-  echo "Failed to parse ${conf}/cluster.yaml"
+function parse_fail() {
+  echo "$(red ERROR): Failed to parse $(yellow "$conf/cluster.yaml")"
   exit 1
 }
 
-isDebug() {
+function isDebug() {
   [[ $DEBUG == 1 ]]
 }
 
-debug() {
-  isDebug && echo "${@@P}"
+# if debug is on, print and return true; otherwise, return false
+function debug() {
+  isDebug && echo "$(blue DEBUG): $(cyan "$*")"
 }
 
-debugAndRun() {
-  debug "$@"
-  if ! isDebug; then
-    # shellcheck disable=SC2294
-    eval "${@@P}"
-  fi
+function quote() {
+  # use getopt to quote, because it uses quotes, rather than escaping spaces, 
and that's easier to
+  # read; POSIXLY_CORRECT makes it ignore unrecognized options, so it still 
quotes things that start
+  # with '-', but it only does this after the first non-option argument, so we 
provide an empty
+  # string to trick it, and then we remove it in the regex that removes " -- 
'' " from the front of
+  # the parsed output, which is now quoted
+  local quoted
+  quoted=$(POSIXLY_CORRECT=1 getopt -o "" -- '' "$@") &&
+    [[ $quoted =~ ^[[:space:]]*--[[:space:]]*\'\'[[:space:]]*(.*)$ ]] && echo 
"${BASH_REMATCH[1]}" &&
+    return
+  echo "$(red ERROR): $(yellow internal script error): unable to quote: 
$(yellow "$*")"
+  exit 1
 }
 
-function parse_config {
-
-  for file in slaves tservers monitor tracers gc managers masters; do
-    if [[ -f ${conf}/${file} ]]; then
-      echo "ERROR: A '${file}' file was found in ${conf}/"
-      echo "Accumulo now uses cluster host configuration information from 
'cluster.yaml' and requires that the '${file}' file not be present to reduce 
confusion."
-      exit 1
-    fi
-  done
-
-  if [[ ! -f ${conf}/cluster.yaml ]]; then
-    echo "ERROR: A 'cluster.yaml' file was not found at ${conf}/cluster.yaml"
-    echo "Please make sure it exists and is configured with the host 
information. Run 'accumulo-cluster create-config' to create an example 
configuration."
-    exit 1
-  fi
+# call debug to print, or execute if debug is off
+function debugOrRun() {
+  debug "$(quote "$@")" || "$@"
+}
 
-  trap 'rm -f "$CONFIG_FILE"' EXIT
-  CONFIG_FILE=$(mktemp) || exit 1
-  ${accumulo_cmd} org.apache.accumulo.core.conf.cluster.ClusterConfigParser 
"${conf}"/cluster.yaml "$CONFIG_FILE" || parse_fail
-  #shellcheck source=/dev/null
-  . "$CONFIG_FILE"
-  debug "Parsed config:" && cat "$CONFIG_FILE"
-  rm -f "$CONFIG_FILE"
+function canRunOnHost() {
 
-  if [[ -z $MANAGER_HOSTS ]]; then
-    echo "ERROR: managers not found in ${conf}/cluster.yaml"
-    exit 1
+  # always true when non-local
+  if [[ $ARG_LOCAL == 0 ]]; then
+    return 0
   fi
 
-  if [[ -z $TSERVER_HOSTS ]]; then
-    echo "ERROR: tservers not found in ${conf}/cluster.yaml"
+  if [[ -z $1 ]]; then
+    echo "$(red ERROR): Host argument expected but missing"
     exit 1
   fi
 
-  if [[ -n $COMPACTION_QUEUES && -z $COORDINATOR_HOSTS ]]; then
-    echo "WARN: External compaction queues configured, but no coordinator 
configured"
-  fi
-
-  for queue in $COMPACTION_QUEUES; do
-    Q="COMPACTOR_HOSTS_${queue}"
-    if [[ -z ${!Q} ]]; then
-      echo "WARN: External compaction queue $queue configured, but no 
compactors configured for it"
+  local found=0
+  local addr
+  for addr in "${LOCAL_HOST_ADDRESSES[@]}"; do
+    if [[ $1 == "$addr" ]]; then
+      found=1
+      break
     fi
   done
+  [[ $found == 1 ]]
+}
 
-  for group in $SSERVER_GROUPS; do
-    G="SSERVER_HOSTS_${group}"
-    if [[ -z ${!G} ]]; then
-      echo "WARN: ScanServer group $group configured, but no hosts configured 
for it"
+function parse_config() {
+
+  for file in slaves tservers monitor tracers gc managers masters; do
+    if [[ -f $conf/$file ]]; then
+      echo "$(red ERROR): A $(yellow "$file") file was found in $(yellow 
"$conf/")"
+      echo "$(red ERROR): Accumulo now uses cluster host configuration 
information from $(yellow cluster.yaml)"
+      echo "$(red ERROR): and requires that the $(yellow "$file") file not be 
present to reduce confusion."
+      exit 1
     fi
   done
 
-  unset manager1
-  manager1=$(echo "${MANAGER_HOSTS}" | cut -d" " -f1)
+  local manager1
+  local tservers_found
+  local group
+  local G
 
-  if [[ -z $MONITOR_HOSTS ]]; then
-    echo "WARN: monitors not found in ${conf}/cluster.yaml, using first 
manager host $manager1"
-    MONITOR_HOSTS=$manager1
+  if [[ ! -f $conf/cluster.yaml ]]; then
+    echo "$(red ERROR): File not found $(yellow "$conf/cluster.yaml")"
+    echo "$(red ERROR): Please make sure it exists and is configured with the 
host information."
+    echo "$(red ERROR): Run $(yellow accumulo-cluster create-config) to create 
an example configuration."
+    exit 1
   fi
 
-  if [[ -z $GC_HOSTS ]]; then
-    echo "WARN: gc not found in ${conf}/cluster.yaml, using first manager host 
$manager1"
-    GC_HOSTS=$manager1
+  trap 'rm -f "$CONFIG_FILE"' EXIT
+  CONFIG_FILE=$(mktemp --tmpdir "ClusterConfigParser-XXXXXXXX.out") || exit 1
+  "$accumulo_cmd" org.apache.accumulo.core.conf.cluster.ClusterConfigParser 
"$conf/cluster.yaml" "$CONFIG_FILE" || parse_fail
+  #shellcheck source=/dev/null
+  . "$CONFIG_FILE"
+  debug "Parsed config from $(white "$conf/cluster.yaml")"
+  local line
+  if isDebug; then
+    while read -r line; do
+      debug "$(white "$line")"
+    done <"$CONFIG_FILE"
   fi
+  rm -f "$CONFIG_FILE"
 
-  if [[ -z $NUM_TSERVERS ]]; then
-    echo "INFO: ${NUM_TSERVERS} tservers will be started per host"
+  # this might not be possible, since the ClusterConfigParser would probably 
fail instead
+  if [[ -z $MANAGER_HOSTS ]]; then
+    echo "$(red ERROR): $(yellow managers) not found in $(yellow 
"$conf/cluster.yaml")"
+    exit 1
   fi
 
+  # This version doesn't support configurable tserver groups, so
+  # use a default group to allow code reuse with newer branches
+  # Shellcheck thinks these aren't used, but they are referenced
+  # indirectly by group name.
+  TSERVER_GROUPS="default"
+  #shellcheck disable=SC2034
+  TSERVERS_PER_HOST_default=${NUM_TSERVERS:-1}
+  #shellcheck disable=SC2034
+  TSERVER_HOSTS_default=$TSERVER_HOSTS
+
+  # Rename variables from this version's config parser to ones that
+  # this script expects, to support code reuse with newer branches
+  COMPACTOR_GROUPS=$COMPACTION_QUEUES
+  local var_name
   for group in $SSERVER_GROUPS; do
-    var_name="NUM_SSERVERS_${group}"
+    var_name="NUM_SSERVERS_$group"
     if [[ -n ${!var_name} ]]; then
-      echo "INFO: ${!var_name} scan servers will be started per host for group 
${group}"
+      declare "SSERVERS_PER_HOST_$group"="${!var_name}"
+    else
+      declare "SSERVERS_PER_HOST_$group"="${NUM_SSERVERS:-1}"
     fi
   done
-
-  for queue in $COMPACTION_QUEUES; do
-    var_name="NUM_COMPACTORS_${queue}"
+  for group in $COMPACTOR_GROUPS; do
+    var_name="NUM_COMPACTORS_$group"
     if [[ -n ${!var_name} ]]; then
-      echo "INFO: ${!var_name} compactors will be started per host for queue 
${queue}"
-    fi
-  done
-}
-
-function control_service() {
-  control_cmd="$1"
-  host="$2"
-  service="$3"
-
-  local last_instance_id
-  last_instance_id=1
-  [[ $service == "tserver" ]] && last_instance_id=${NUM_TSERVERS:-1}
-  [[ $service == "sserver" ]] && last_instance_id=${NUM_SSERVERS:-1}
-  [[ $service == "compactor" ]] && last_instance_id=${NUM_COMPACTORS:-1}
-
-  for ((inst_id = 1; inst_id <= last_instance_id; inst_id++)); do
-    # Only increment the service name when more than one service is desired.
-    ACCUMULO_SERVICE_INSTANCE=""
-    [[ last_instance_id -gt 1 ]] && ACCUMULO_SERVICE_INSTANCE="${inst_id}"
-    [[ $service =~ ^compactor|sserver$ ]] && 
ACCUMULO_SERVICE_INSTANCE="${ACCUMULO_SERVICE_INSTANCE}_${5}"
-
-    if [[ $host == localhost || $host == "$(hostname -s)" || $host == 
"$(hostname -f)" || "$(hostname -I)" =~ $host ]]; then
-      #
-      # The server processes take arguments (e.g. -p, -o, -q [in the case of 
the Compactor]). Always add the -a argument
-      # using the value of $host
-      #
-      if [[ $# -gt 3 ]]; then
-        debugAndRun ACCUMULO_SERVICE_INSTANCE="${ACCUMULO_SERVICE_INSTANCE}" 
"${bin}/accumulo-service" "$service" "$control_cmd" "-a" "$host" "${@:4}"
-      else
-        debugAndRun ACCUMULO_SERVICE_INSTANCE="${ACCUMULO_SERVICE_INSTANCE}" 
"${bin}/accumulo-service" "$service" "$control_cmd" "-a" "$host"
-      fi
+      declare "COMPACTORS_PER_HOST_$group"="${!var_name}"
     else
-      if [[ $# -gt 3 ]]; then
-        EXTRA_ARGS="${*:4}"
-        debugAndRun "$SSH" "$host" "bash -c 
'ACCUMULO_SERVICE_INSTANCE=${ACCUMULO_SERVICE_INSTANCE} ${bin}/accumulo-service 
\"$service\" \"$control_cmd\" \"-a\" \"$host\" $EXTRA_ARGS '"
-      else
-        debugAndRun "$SSH" "$host" "bash -c 
'ACCUMULO_SERVICE_INSTANCE=${ACCUMULO_SERVICE_INSTANCE} ${bin}/accumulo-service 
\"$service\" \"$control_cmd\" \"-a\" \"$host\"'"
-      fi
+      declare "COMPACTORS_PER_HOST_$group"="${NUM_COMPACTORS:-1}"
     fi
   done
-}
 
-function start_service() {
-  control_service start "$@"
-}
+  # This version requires a compaction coordinator for compactors
+  if [[ -n $COMPACTOR_GROUPS && -z $COORDINATOR_HOSTS ]]; then
+    echo "$(yellow WARN): External compactor group(s) configured, but no 
coordinator configured"
+  fi
 
-function start_compactors() {
-  if [[ -z $COMPACTION_QUEUES ]]; then
-    echo "No compactor queues configured..."
+  if [[ -z $COMPACTOR_GROUPS ]]; then
+    # no compactor groups are required for this version
+    debug "No compactor groups configured"
   else
-    echo -n "Starting compactor servers ..."
-    queues=$COMPACTION_QUEUES
-    if [[ -n $1 ]]; then
-      queues="$1"
-      echo "Only starting servers for group: ${queues}"
-    fi
-    for queue in $queues; do
-      var_name="NUM_COMPACTORS_${queue}"
-      [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-      Q="COMPACTOR_HOSTS_${queue}"
-      if [[ -n ${!Q} ]]; then
-        for compactor in ${!Q}; do
-          start_service "$compactor" compactor "-q" "$queue"
-        done
-      else
-        echo "${queue} is not a valid queue ...exiting"
+    for group in $COMPACTOR_GROUPS; do
+      G="COMPACTOR_HOSTS_$group"
+      if [[ -z ${!G} ]]; then
+        echo "$(yellow WARN): External compactor group $(yellow "$group") 
configured, but no compactors configured for it"
       fi
     done
   fi
-}
 
-function stop_compactors() {
-  if [[ -z $COMPACTION_QUEUES ]]; then
-    echo "No compactor queues configured..."
+  tservers_found="false"
+  if [[ -z $TSERVER_GROUPS ]]; then
+    echo "$(yellow WARN): No tablet server groups configured"
   else
-    echo "Stopping compactor servers ..."
-    queues=$COMPACTION_QUEUES
-    if [[ -n $1 ]]; then
-      queues="$1"
-      echo "Only stopping servers for group: ${queues}"
-    fi
-    for queue in $queues; do
-      var_name="NUM_COMPACTORS_${queue}"
-      [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-      Q="COMPACTOR_HOSTS_${queue}"
-      if [[ -n ${!Q} ]]; then
-        for compactor in ${!Q}; do
-          stop_service "$compactor" compactor "-q" "$queue"
-        done
+    for group in $TSERVER_GROUPS; do
+      G="TSERVER_HOSTS_$group"
+      if [[ -z ${!G} ]]; then
+        echo "$(yellow WARN): tablet server group $(yellow "$group") 
configured, but no hosts configured for it"
       else
-        echo "${queue} is not a valid compaction queue ...exiting"
+        tservers_found="true"
       fi
     done
   fi
-}
 
-function start_sservers() {
-  if [[ -z $SSERVER_GROUPS ]]; then
-    echo "No scan server groups configured..."
-  else
-    echo "Starting scan servers ..."
-    groups=$SSERVER_GROUPS
-    if [[ -n $1 ]]; then
-      groups="$1"
-      echo "Only starting servers for group: ${groups}"
-    fi
-    for group in $groups; do
-      var_name="NUM_SSERVERS_${group}"
-      [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-      G="SSERVER_HOSTS_${group}"
-      if [[ -n ${!G} ]]; then
-        for sserver in ${!G}; do
-          start_service "$sserver" sserver "-g" "$group"
-        done
-      else
-        echo "${group} is not a valid resource group ...exiting"
-      fi
-    done
+  if [[ $tservers_found != "true" ]]; then
+    echo "$(red ERROR): There are no tablet servers configured, Accumulo 
requires at least $(yellow 1) tablets server to host system tables"
+    exit 1
   fi
-}
 
-function stop_sservers() {
-  if [[ -z $SSERVER_GROUPS ]]; then
-    echo "No scan server groups configured..."
-  else
-    echo "Stopping scan servers ..."
-    groups=$SSERVER_GROUPS
-    if [[ -n $1 ]]; then
-      groups="$1"
-      echo "Only stopping servers for group: ${groups}"
-    fi
-    for group in $groups; do
-      var_name="NUM_SSERVERS_${group}"
-      [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-      G="SSERVER_HOSTS_${group}"
-      if [[ -n ${!G} ]]; then
-        for sserver in ${!G}; do
-          stop_service "$sserver" sserver "-g" "$group"
-        done
-      else
-        echo "${group} is not a valid resource group ...exiting"
+  if [[ -n $SSERVER_GROUPS ]]; then
+    for group in $SSERVER_GROUPS; do
+      G="SSERVER_HOSTS_$group"
+      if [[ -z ${!G} ]]; then
+        echo "$(yellow WARN): scan server group $(yellow "$group") configured, 
but no hosts configured for it"
       fi
     done
   fi
-}
 
-function start_tservers() {
-  echo -n "Starting tablet servers ..."
-  count=1
-  for server in $TSERVER_HOSTS; do
-    echo -n "."
-    start_service "$server" tserver &
-    if ((++count % 72 == 0)); then
-      echo
-      wait
-    fi
-  done
-  echo " done"
-}
-
-function start_all() {
-  unset DISPLAY
+  manager1=$(echo "$MANAGER_HOSTS" | cut -d" " -f1)
 
-  if [[ $1 != "--no-tservers" ]]; then
-    start_tservers
+  if [[ -z $MONITOR_HOSTS ]]; then
+    echo "$(yellow WARN): monitors not found in $(yellow 
"$conf/cluster.yaml"), using first manager host $(green "$manager1")"
+    MONITOR_HOSTS=$manager1
   fi
 
-  for manager in $MANAGER_HOSTS; do
-    start_service "$manager" manager
-  done
+  if [[ -z $GC_HOSTS ]]; then
+    echo "$(yellow WARN): gc not found in $(yellow "$conf/cluster.yaml"), 
using first manager host $(green "$manager1")"
+    GC_HOSTS=$manager1
+  fi
 
-  for gc in $GC_HOSTS; do
-    start_service "$gc" gc
-  done
+}
 
-  for monitor in $MONITOR_HOSTS; do
-    start_service "$monitor" monitor
-  done
+function execute_command() {
+  control_cmd=$1
+  host=$2
+  service=$3
+  group=$4
+  shift 4
 
-  for group in $SSERVER_GROUPS; do
-    var_name="NUM_SSERVERS_${group}"
-    [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-    G="SSERVER_HOSTS_${group}"
-    for sserver in ${!G}; do
-      start_service "$sserver" sserver "-g" "$group"
-    done
-  done
+  local S
+  local servers_per_host
 
-  for coordinator in $COORDINATOR_HOSTS; do
-    start_service "$coordinator" compaction-coordinator
-  done
+  S="${service^^}S_PER_HOST_$group"
+  S="${S//-/_}" # replace dashes in service/group name with underscores
+  servers_per_host="${!S:-1}"
 
-  for queue in $COMPACTION_QUEUES; do
-    var_name="NUM_COMPACTORS_${queue}"
-    [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-    Q="COMPACTOR_HOSTS_${queue}"
-    for compactor in ${!Q}; do
-      start_service "$compactor" compactor "-q" "$queue"
-    done
-  done
+  if [[ $ARG_LOCAL == 1 ]]; then
+    debugOrRun bash -c "ACCUMULO_CLUSTER_ARG=$servers_per_host 
\"$bin/accumulo-service\" $service $control_cmd -a $host $*"
+  else
+    debugOrRun "${SSH[@]}" "$host" "bash -c 
'ACCUMULO_CLUSTER_ARG=$servers_per_host \"$bin/accumulo-service\" $service 
$control_cmd -a $host $*'"
+  fi
+}
 
+function get_localhost_addresses() {
+  local localaddresses
+  local localinterfaces
+  local x
+  if [[ -n $ACCUMULO_LOCALHOST_ADDRESSES ]]; then
+    read -r -a localaddresses <<<"$ACCUMULO_LOCALHOST_ADDRESSES"
+  else
+    read -r -a localinterfaces <<<"$(hostname -I)"
+    read -r -a localaddresses <<<"$(getent hosts 127.0.0.1 ::1 
"${localinterfaces[@]}" | paste -sd' ')"
+  fi
+  for x in "${localaddresses[@]}"; do echo "$x"; done | sort -u
 }
 
-function start_here() {
+function control_services() {
+  unset DISPLAY
+  local operation=$1
 
-  local_hosts="$(hostname -a 2>/dev/null) $(hostname) localhost 127.0.0.1 
$(hostname -I)"
+  if [[ $operation != "start" && $operation != "stop" && $operation != "kill" 
]]; then
+    echo "$(red ERROR): Invalid operation: $(yellow "$operation")"
+    exit 1
+  fi
 
-  for host in $local_hosts; do
-    for tserver in $TSERVER_HOSTS; do
-      if echo "$tserver" | grep -q "^${host}\$"; then
-        start_service "$host" tserver
-        break
+  local tserver_groups
+  local addr
+  local group
+  local tserver
+  local G
+  if [[ $ARG_ALL == 1 && $ARG_LOCAL == 0 && $operation == "stop" ]]; then
+    echo "Stopping Accumulo cluster..."
+    if ! isDebug; then
+      if ! "$accumulo_cmd" admin stopAll; then
+        echo "Invalid password or unable to connect to the manager"
+        echo "Initiating forced shutdown in 15 seconds (Ctrl-C to abort)"
+        sleep 10
+        echo "Initiating forced shutdown in  5 seconds (Ctrl-C to abort)"
+      else
+        echo "Accumulo shut down cleanly"
+        echo "Utilities and unresponsive servers will shut down in 5 seconds 
(Ctrl-C to abort)"
       fi
+      sleep 5
+    fi
+  elif [[ $ARG_LOCAL == 1 && $ARG_TSERVER == 1 && $operation == "stop" ]]; then
+    tserver_groups=$TSERVER_GROUPS
+    if [[ -n $ARG_TSERVER_GROUP ]]; then
+      tserver_groups=$ARG_TSERVER_GROUP
+    fi
+    for addr in "${LOCAL_HOST_ADDRESSES[@]}"; do
+      for group in $tserver_groups; do
+        G="TSERVER_HOSTS_$group"
+        for tserver in ${!G}; do
+          if echo "$tserver" | grep -q "$addr"; then
+            if ! isDebug; then
+              "$accumulo_cmd" admin stop "$addr"
+            else
+              debug "Stopping tservers on $addr via admin command"
+            fi
+          fi
+        done
+      done
     done
-  done
+  elif [[ $ARG_ALL == 1 && $operation == "kill" ]]; then
+    echo "Killing Accumulo cluster..."
+  fi
 
-  for host in $local_hosts; do
+  local count
+  local hosts
+  if [[ $ARG_ALL == 1 || $ARG_TSERVER == 1 ]]; then
+    tserver_groups=$TSERVER_GROUPS
+    if [[ -n $ARG_TSERVER_GROUP ]]; then
+      tserver_groups=$ARG_TSERVER_GROUP
+    fi
+    for group in $tserver_groups; do
+      local msg
+      local msgNoColor
+      msg="Executing $(green "$ARG_CMD") on $(purple tablet servers) for group 
$(yellow "$group") ..."
+      msgNoColor=$(decolorize "$msg")
+      count=${#msgNoColor}
+      ((count > 71)) && count=69 # only print up to 3 more dots if the line is 
too long
+      echo -n "$msg"
+      hosts="TSERVER_HOSTS_$group"
+      for tserver in ${!hosts}; do
+        if canRunOnHost "$tserver"; then
+          echo -n "."
+          execute_command "$operation" "$tserver" tserver "$group"
+          if ((++count % 72 == 0)); then
+            echo
+            wait
+          fi
+        fi
+      done
+    done
+    echo "done"
+  fi
+
+  local manager
+  if [[ $ARG_ALL == 1 || $ARG_MANAGER == 1 ]]; then
     for manager in $MANAGER_HOSTS; do
-      if echo "$manager" | grep -q "^${host}\$"; then
-        start_service "$host" manager
-        break
+      if canRunOnHost "$manager"; then
+        echo "Executing $(green "$ARG_CMD") on $(purple managers)"
+        execute_command "$operation" "$manager" manager "default"
       fi
     done
-  done
+  fi
 
-  for host in $local_hosts; do
+  local gc
+  if [[ $ARG_ALL == 1 || $ARG_GC == 1 ]]; then
     for gc in $GC_HOSTS; do
-      if echo "$gc" | grep -q "^${host}\$"; then
-        start_service "$host" gc
-        break
+      if canRunOnHost "$gc"; then
+        echo "Executing $(green "$ARG_CMD") on $(purple garbage collectors)"
+        execute_command "$operation" "$gc" gc "default"
       fi
     done
-  done
+  fi
 
-  for host in $local_hosts; do
+  local monitor
+  if [[ $ARG_ALL == 1 || $ARG_MONITOR == 1 ]]; then
     for monitor in $MONITOR_HOSTS; do
-      if echo "$monitor" | grep -q "^${host}\$"; then
-        start_service "$host" monitor
-        break
+      if canRunOnHost "$monitor"; then
+        echo "Executing $(green "$ARG_CMD") on $(purple monitors)"
+        execute_command "$operation" "$monitor" monitor "default"
       fi
     done
-  done
-
-  for group in $SSERVER_GROUPS; do
-    var_name="NUM_SSERVERS_${group}"
-    [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-    for host in $local_hosts; do
-      G="SSERVER_HOSTS_${group}"
-      for sserver in ${!G}; do
-        if echo "$sserver" | grep -q "^${host}\$"; then
-          start_service "$sserver" sserver "-g" "$group"
-        fi
-      done
-    done
-  done
+  fi
 
-  for host in $local_hosts; do
+  local coordinator
+  if [[ $ARG_ALL == 1 || $ARG_COORDINATOR == 1 ]]; then
     for coordinator in $COORDINATOR_HOSTS; do
-      if echo "$coordinator" | grep -q "^${host}\$"; then
-        start_service "$coordinator" compaction-coordinator
+      if canRunOnHost "$coordinator"; then
+        echo "Executing $(green "$ARG_CMD") on $(purple compaction 
coordinators)"
+        execute_command "$operation" "$coordinator" compaction-coordinator 
"default"
       fi
     done
-  done
+  fi
 
-  for queue in $COMPACTION_QUEUES; do
-    for host in $local_hosts; do
-      var_name="NUM_COMPACTORS_${queue}"
-      [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-      Q="COMPACTOR_HOSTS_${queue}"
-      for compactor in ${!Q}; do
-        if echo "$compactor" | grep -q "^${host}\$"; then
-          start_service "$compactor" compactor "-q" "$queue"
+  local sserver_groups
+  local sserver
+  if [[ $ARG_ALL == 1 || $ARG_SSERVER == 1 ]]; then
+    sserver_groups=$SSERVER_GROUPS
+    if [[ -n $ARG_SSERVER_GROUP ]]; then
+      sserver_groups=$ARG_SSERVER_GROUP
+    fi
+    for group in $sserver_groups; do
+      echo "Executing $(green "$ARG_CMD") on $(purple scan servers) for group 
$(yellow "$group")"
+      hosts="SSERVER_HOSTS_$group"
+      for sserver in ${!hosts}; do
+        if canRunOnHost "$sserver"; then
+          execute_command "$operation" "$sserver" sserver "$group" "-g" 
"$group"
         fi
       done
     done
-  done
-
-}
-
-function end_service() {
-  control_service "$@"
-}
-
-function stop_service() {
-  end_service "stop" "$@"
-}
-
-function kill_service() {
-  end_service "kill" "$@"
-}
-
-function stop_tservers() {
-
-  echo "Stopping unresponsive tablet servers (if any)..."
-  for host in $TSERVER_HOSTS; do
-    stop_service "$host" tserver &
-  done
-
-  sleep 10
-
-  echo "Stopping unresponsive tablet servers hard (if any)..."
-  for host in $TSERVER_HOSTS; do
-    kill_service "$host" tserver &
-  done
-
-  echo "Cleaning tablet server entries from zookeeper"
-  if ! isDebug; then
-    ${accumulo_cmd} org.apache.accumulo.server.util.ZooZap -tservers
-  fi
-}
-
-function kill_all() {
-  echo "Killing Accumulo cluster..."
-
-  for manager in $MANAGER_HOSTS; do
-    kill_service "$manager" manager
-  done
-
-  for gc in $GC_HOSTS; do
-    kill_service "$gc" gc
-  done
-
-  for monitor in $MONITOR_HOSTS; do
-    kill_service "$monitor" monitor
-  done
-
-  for group in $SSERVER_GROUPS; do
-    var_name="NUM_SSERVERS_${group}"
-    [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-    G="SSERVER_HOSTS_${group}"
-    for sserver in ${!G}; do
-      kill_service "$sserver" sserver "-g" "$group"
-    done
-  done
-
-  for host in $TSERVER_HOSTS; do
-    kill_service "$host" tserver
-  done
-
-  for coordinator in $COORDINATOR_HOSTS; do
-    kill_service "$coordinator" compaction-coordinator
-  done
-
-  for queue in $COMPACTION_QUEUES; do
-    var_name="NUM_COMPACTORS_${queue}"
-    [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-    Q="COMPACTOR_HOSTS_${queue}"
-    for compactor in ${!Q}; do
-      kill_service "$compactor" compactor "-q" "$queue"
-    done
-  done
-
-  echo "Cleaning all server entries in ZooKeeper"
-  if ! isDebug; then
-    ${accumulo_cmd} org.apache.accumulo.server.util.ZooZap -manager -tservers 
-compaction-coordinators -compactors -sservers
   fi
-}
 
-function stop_all() {
-  echo "Stopping Accumulo cluster..."
-  if ! isDebug; then
-    if ! ${accumulo_cmd} admin stopAll; then
-      echo "Invalid password or unable to connect to the manager"
-      echo "Initiating forced shutdown in 15 seconds (Ctrl-C to abort)"
-      sleep 10
-      echo "Initiating forced shutdown in  5 seconds (Ctrl-C to abort)"
-    else
-      echo "Accumulo shut down cleanly"
-      echo "Utilities and unresponsive servers will shut down in 5 seconds 
(Ctrl-C to abort)"
+  local compactor_groups
+  local compactor
+  if [[ $ARG_ALL == 1 || $ARG_COMPACTOR == 1 ]]; then
+    compactor_groups=$COMPACTOR_GROUPS
+    if [[ -n $ARG_COMPACTOR_GROUP ]]; then
+      compactor_groups=$ARG_COMPACTOR_GROUP
     fi
-    sleep 5
-  fi
-
-  # Look for processes not killed by 'admin stopAll'
-
-  for end_cmd in "stop" "kill"; do
-
-    for manager in $MANAGER_HOSTS; do
-      end_service $end_cmd "$manager" manager
-    done
-
-    for gc in $GC_HOSTS; do
-      end_service $end_cmd "$gc" gc
-    done
-
-    for monitor in $MONITOR_HOSTS; do
-      end_service $end_cmd "$monitor" monitor
-    done
-
-    for group in $SSERVER_GROUPS; do
-      var_name="NUM_SSERVERS_${group}"
-      [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-      G="SSERVER_HOSTS_${group}"
-      for sserver in ${!G}; do
-        end_service $end_cmd "$sserver" sserver "-g" "$group"
-      done
-    done
-
-    for coordinator in $COORDINATOR_HOSTS; do
-      end_service $end_cmd "$coordinator" compaction-coordinator
-    done
-
-    for queue in $COMPACTION_QUEUES; do
-      var_name="NUM_COMPACTORS_${queue}"
-      [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-      Q="COMPACTOR_HOSTS_${queue}"
-      for compactor in ${!Q}; do
-        end_service $end_cmd "$compactor" compactor "-q" "$queue"
+    for group in $compactor_groups; do
+      echo "Executing $(green "$ARG_CMD") on $(purple compactors) for group 
$(yellow "$group")"
+      hosts="COMPACTOR_HOSTS_$group"
+      for compactor in ${!hosts}; do
+        if canRunOnHost "$compactor"; then
+          execute_command "$operation" "$compactor" compactor "$group" "-q" 
"$group"
+        fi
       done
     done
-
-  done
-
-  # stop tserver still running
-  stop_tservers
-
-  echo "Cleaning all server entries in ZooKeeper"
-  if ! isDebug; then
-    ${accumulo_cmd} org.apache.accumulo.server.util.ZooZap -manager -tservers 
-compaction-coordinators -compactors -sservers
   fi
-}
 
-function stop_here() {
-  # Determine hostname without errors to user
-  hosts_to_check=("$(hostname -a 2>/dev/null | head -1)" "$(hostname -f)")
-
-  if echo "${TSERVER_HOSTS}" | grep -Eq 'localhost|127[.]0[.]0[.]1'; then
+  if [[ $ARG_LOCAL == 0 && $ARG_ALL == 1 && ($operation == "stop" || 
$operation == "kill") ]]; then
     if ! isDebug; then
-      ${accumulo_cmd} admin stop localhost
-    else
-      debug "Stopping tservers on localhost via admin command"
+      echo "Cleaning all server entries in ZooKeeper"
+      "$accumulo_cmd" org.apache.accumulo.server.util.ZooZap -manager 
-tservers -compaction-coordinators -compactors -sservers
     fi
-  else
-    for host in "${hosts_to_check[@]}"; do
-      for tserver in $TSERVER_HOSTS; do
-        if echo "$tserver" | grep -q "$host"; then
-          if ! isDebug; then
-            ${accumulo_cmd} admin stop "$host"
-          else
-            debug "Stopping tservers on $host via admin command"
-          fi
-        fi
-      done
-    done
   fi
 
-  for host in "${hosts_to_check[@]}"; do
-    for end_cmd in "stop" "kill"; do
-      for svc in tserver gc manager monitor compaction-coordinator; do
-        end_service $end_cmd "$host" $svc
-      done
-      for group in $SSERVER_GROUPS; do
-        var_name="NUM_SSERVERS_${group}"
-        [[ -n ${!var_name} ]] && NUM_SSERVERS=${!var_name}
-        G="SSERVER_HOSTS_${group}"
-        for sserver in ${!G}; do
-          end_service $end_cmd "$sserver" sserver "-g" "$group"
-        done
-      done
-      for queue in $COMPACTION_QUEUES; do
-        var_name="NUM_COMPACTORS_${queue}"
-        [[ -n ${!var_name} ]] && NUM_COMPACTORS=${!var_name}
-        Q="COMPACTOR_HOSTS_${queue}"
-        for compactor in ${!Q}; do
-          end_service $end_cmd "$host" compactor "-q" "$queue"
-        done
-      done
-    done
-  done
 }
 
 function main() {
 
-  if [[ -z $1 ]]; then
-    invalid_args "<command> cannot be empty"
-  fi
+  checkTerminalSupportsColor && COLOR_ENABLED=1 || COLOR_ENABLED=0
+  parse_args "$@"
 
   # Resolve base directory
+  local SOURCE
   SOURCE="${BASH_SOURCE[0]}"
-  while [ -h "${SOURCE}" ]; do
-    bin="$(cd -P "$(dirname "${SOURCE}")" && pwd)"
-    SOURCE="$(readlink "${SOURCE}")"
-    [[ ${SOURCE} != /* ]] && SOURCE="${bin}/${SOURCE}"
+  while [[ -L $SOURCE ]]; do
+    bin="$(cd -P "$(dirname "$SOURCE")" && pwd)"
+    SOURCE="$(readlink "$SOURCE")"
+    [[ $SOURCE != /* ]] && SOURCE="$bin/$SOURCE"
   done
-  bin="$(cd -P "$(dirname "${SOURCE}")" && pwd)"
-  basedir=$(cd -P "${bin}"/.. && pwd)
-  conf="${ACCUMULO_CONF_DIR:-${basedir}/conf}"
-
-  accumulo_cmd="${bin}/accumulo"
-  SSH='ssh -qnf -o ConnectTimeout=2'
+  bin="$(cd -P "$(dirname "$SOURCE")" && pwd)"
+  basedir=$(cd -P "$bin/.." && pwd)
+  conf="${ACCUMULO_CONF_DIR:-$basedir/conf}"
 
-  # Copy input arguments into new array
-  # removing any options
-  DEBUG=0
-  i=0
-  declare -a program_args
-  for arg in "$@"; do
-    if [[ $arg == "--dry-run" ]]; then
-      DEBUG=1
-    else
-      program_args[i++]="$arg"
-    fi
-  done
+  accumulo_cmd="$bin/accumulo"
+  SSH=('ssh' '-qnf' '-o' 'ConnectTimeout=2')
 
-  debug "debug: ${DEBUG} args: ${program_args[*]}"
+  mapfile -t LOCAL_HOST_ADDRESSES < <(get_localhost_addresses)
+  debug "LOCAL_HOST_ADDRESSES=${LOCAL_HOST_ADDRESSES[*]}"
 
-  case "${program_args[0]}" in
+  case "$ARG_CMD" in
     create-config)
       if [[ -f "$conf"/cluster.yaml ]]; then
-        echo "ERROR : ${conf}/cluster.yaml already exists, not overwriting"
+        echo "ERROR : $conf/cluster.yaml already exists, not overwriting"
         exit 1
       fi
       cat <<EOF >"$conf"/cluster.yaml
@@ -698,103 +699,80 @@ tserver:
 # compactors_per_host.
 #
 tservers_per_host: 1
-#sservers_per_host: 
+#sservers_per_host:
 # - default: 1
 #compactors_per_host:
 # - q1: 1
-# - q2: 1 
+# - q2: 1
 
 EOF
       ;;
     restart)
       parse_config
-      stop_all
+      control_services stop
+      control_services kill
       # Make sure the JVM has a chance to fully exit
       sleep 1
-      start_all
+      control_services start
       ;;
     start)
       parse_config
-      start_all
+      control_services start
       ;;
     stop)
       parse_config
-      stop_all
+      control_services stop
       ;;
     kill)
       parse_config
-      kill_all
-      ;;
-    start-non-tservers)
-      echo "$1 is deprecated. Please use \`start-servers --no-tservers\` 
instead"
-      parse_config
-      start_all --no-tservers
-      ;;
-    start-tservers)
-      echo "$1 is deprecated. Please use \`start-servers --tservers\` instead"
-      parse_config
-      start_tservers
+      control_services kill
       ;;
     start-here)
       parse_config
-      start_here
+      ARG_ALL=1
+      ARG_LOCAL=1
+      control_services start
       ;;
-    stop-tservers)
-      echo "$1 is deprecated. Please use \`stop-servers --tservers\` instead"
+    stop-here)
       parse_config
-      stop_tservers
+      ARG_ALL=1
+      ARG_LOCAL=1
+      control_services stop
+      control_services kill
       ;;
-    stop-here)
+    start-non-tservers)
+      echo "'$ARG_CMD' is deprecated. Please specify the servers you wish to 
start instead"
       parse_config
-      stop_here
+      ARG_MANAGER=1
+      ARG_GC=1
+      ARG_MONITOR=1
+      ARG_COORDINATOR=1
+      ARG_SSERVER=1
+      ARG_COMPACTOR=1
+      control_services start
       ;;
     start-servers)
+      echo "'$ARG_CMD' is deprecated. Please use 'start' instead"
       parse_config
-      subcommand="${program_args[1]}"
-      case "$subcommand" in
-        "--all" | "")
-          start_all
-          ;;
-        "--tservers")
-          start_tservers
-          ;;
-        "--no-tservers")
-          start_all --no-tservers
-          ;;
-        "--sservers")
-          start_sservers "${program_args[@]:2}"
-          ;;
-        "--compactors")
-          start_compactors "${program_args[@]:2}"
-          ;;
-        *)
-          invalid_args "'$subcommand' is an invalid <command>"
-          ;;
-      esac
+      control_services start
       ;;
     stop-servers)
+      echo "'$ARG_CMD' is deprecated. Please use 'stop' instead"
       parse_config
-      subcommand="${program_args[1]}"
-      case "$subcommand" in
-        "--all" | "")
-          stop_all
-          ;;
-        "--tservers")
-          stop_tservers
-          ;;
-        "--sservers")
-          stop_sservers "${program_args[@]:2}"
-          ;;
-        "--compactors")
-          stop_compactors "${program_args[@]:2}"
-          ;;
-        *)
-          invalid_args "'$subcommand' is an invalid <command>"
-          ;;
-      esac
+      control_services stop
+      ;;
+    start-tservers)
+      echo "'$ARG_CMD' is deprecated. Please use 'start --tservers' instead"
+      ARG_TSERVER=1
+      control_services start
+      ;;
+    stop-tservers)
+      echo "'$ARG_CMD' is deprecated. Please use 'stop --tservers' instead"
+      ARG_TSERVER=1
+      control_services stop
       ;;
     *)
-      invalid_args "${program_args[0]} is an invalid <command>"
+      invalid_args "'$ARG_CMD' is an invalid <command>"
       ;;
   esac
 }
diff --git a/assemble/bin/accumulo-service b/assemble/bin/accumulo-service
index 390728fddc..2bc754c212 100755
--- a/assemble/bin/accumulo-service
+++ b/assemble/bin/accumulo-service
@@ -26,7 +26,7 @@ Services:
   gc                     Accumulo garbage collector
   monitor                Accumulo monitor
   manager                Accumulo manager
-  master                 Deprecated. Accumulo master
+  master                 Deprecated. Use 'manager' instead
   tserver                Accumulo tserver
   compaction-coordinator Accumulo compaction coordinator (experimental)
   compactor              Accumulo compactor (experimental)
@@ -60,31 +60,69 @@ function rotate_log() {
   fi
 }
 
+function get_group() {
+  # Find the group parameter if any
+  local group="default"
+  local group_param=""
+  local param
+  for param in "$@"; do
+    if [[ -n $group_param ]]; then
+      # grab the group if the previous arg was the group param
+      group="$param"
+      break
+    elif [[ $param == '-q' || $param == '-g' ]]; then
+      # found the group parameter, the next arg is the group
+      group_param=$param
+    fi
+  done
+  echo "$group"
+}
+
 function start_service() {
   local service_type=$1
   local service_name=$2
   shift 2
 
-  local pid_file="${ACCUMULO_PID_DIR}/accumulo-${service_name}.pid"
-  if [[ -f $pid_file ]]; then
-    pid=$(cat "$pid_file")
-    if kill -0 "$pid" 2>/dev/null; then
-      echo "$HOST : ${service_name} already running (${pid})"
-      exit 0
-    fi
+  local build_service_name="false"
+  if [[ -n $service_name ]]; then
+    # if service_name is supplied, then we are only starting one instance
+    servers_per_host=1
+  else
+    build_service_name="true"
+    servers_per_host=${ACCUMULO_CLUSTER_ARG:-1}
   fi
-  echo "Starting $service_name on $HOST"
 
-  if [[ ${service_type} == "manager" ]]; then
-    "${bin}/accumulo" org.apache.accumulo.manager.state.SetGoalState NORMAL
-  fi
-  outfile="${ACCUMULO_LOG_DIR}/${service_name}_${HOST}.out"
-  errfile="${ACCUMULO_LOG_DIR}/${service_name}_${HOST}.err"
-  rotate_log "$outfile"
-  rotate_log "$errfile"
+  for ((process_num = 1; process_num <= servers_per_host; process_num++)); do
+    if [[ ${build_service_name} == "true" ]]; then
+      service_name="${service_type}_${group}_${process_num}"
+    fi
+    # The ACCUMULO_SERVICE_INSTANCE variable is used in
+    # accumulo-env.sh to set parameters on the command
+    # line.
+    export ACCUMULO_SERVICE_INSTANCE="${service_name}"
 
-  nohup "${bin}/accumulo" "$service_type" "$@" >"$outfile" 2>"$errfile" 
</dev/null &
-  echo "$!" >"${pid_file}"
+    local pid_file="${ACCUMULO_PID_DIR}/accumulo-${service_name}.pid"
+    if [[ -f $pid_file ]]; then
+      pid=$(cat "$pid_file")
+      if kill -0 "$pid" 2>/dev/null; then
+        echo "$HOST : ${service_name} already running (${pid})"
+        continue
+      fi
+    fi
+    echo "Starting $service_name on $HOST"
+
+    if [[ ${service_type} == "manager" ]]; then
+      "${bin}/accumulo" org.apache.accumulo.manager.state.SetGoalState NORMAL
+    fi
+    outfile="${ACCUMULO_LOG_DIR}/${service_name}_${HOST}.out"
+    errfile="${ACCUMULO_LOG_DIR}/${service_name}_${HOST}.err"
+    rotate_log "$outfile"
+    rotate_log "$errfile"
+
+    nohup "${bin}/accumulo" "$service_type" "$@" >"$outfile" 2>"$errfile" 
</dev/null &
+    echo "$!" >"${pid_file}"
+
+  done
 
   # Check the max open files limit and selectively warn
   max_files_open=$(ulimit -n)
@@ -127,6 +165,18 @@ function stop_service() {
       local pid_file="${ACCUMULO_PID_DIR}/accumulo-${process}.pid"
       control_process "TERM" "$process" "$pid_file"
     done
+  elif [[ -n $ACCUMULO_CLUSTER_ARG ]]; then
+
+    servers_per_host=${ACCUMULO_CLUSTER_ARG:-1}
+    group=$(get_group "$@")
+
+    for ((process_num = 1; process_num <= servers_per_host; process_num++)); do
+      service_name="${service_type}_${group}_${process_num}"
+      echo "Stopping service process: $service_name"
+      local pid_file="${ACCUMULO_PID_DIR}/accumulo-${service_name}.pid"
+      control_process "TERM" "$service_name" "$pid_file"
+    done
+
   else
     echo "Stopping service process: $service_name"
     local pid_file="${ACCUMULO_PID_DIR}/accumulo-${service_name}.pid"
@@ -144,6 +194,18 @@ function kill_service() {
       local pid_file="${ACCUMULO_PID_DIR}/accumulo-${process}.pid"
       control_process "KILL" "$process" "$pid_file"
     done
+  elif [[ -n $ACCUMULO_CLUSTER_ARG ]]; then
+
+    servers_per_host=${ACCUMULO_CLUSTER_ARG:-1}
+    group=$(get_group "$@")
+
+    for ((process_num = 1; process_num <= servers_per_host; process_num++)); do
+      service_name="${service_type}_${group}_${process_num}"
+      echo "Stopping service process: $service_name"
+      local pid_file="${ACCUMULO_PID_DIR}/accumulo-${service_name}.pid"
+      control_process "KILL" "$service_name" "$pid_file"
+    done
+
   else
     local pid_file="${ACCUMULO_PID_DIR}/accumulo-${service_name}.pid"
     control_process "KILL" "$service_name" "$pid_file"
@@ -182,6 +244,9 @@ function main() {
   export conf="${ACCUMULO_CONF_DIR:-${basedir}/conf}"
   export lib="${basedir}/lib"
 
+  group=$(get_group "$@")
+  export ACCUMULO_RESOURCE_GROUP="$group"
+
   if [[ -f "${conf}/accumulo-env.sh" ]]; then
     #shellcheck source=../conf/accumulo-env.sh
     source "${conf}/accumulo-env.sh"
@@ -200,7 +265,7 @@ function main() {
   local service_type="$1"
   local command_name="$2"
   shift 2
-  local service_name=$service_type
+  local service_name=""
   local all_flag=false
 
   if [[ $service_type == "master" ]]; then
@@ -209,7 +274,7 @@ function main() {
   fi
 
   # Check and see if accumulo-cluster is calling this script
-  if [[ -z $ACCUMULO_SERVICE_INSTANCE ]]; then
+  if [[ -z $ACCUMULO_CLUSTER_ARG ]]; then
     # The rest of the arguments are from a user
     if [[ $1 == "--all" ]]; then
       all_flag=true
@@ -219,9 +284,6 @@ function main() {
         service_name="$1"
       fi
     fi
-  # Use the special bash env var from accumulo-cluster
-  else
-    service_name=${service_type}${ACCUMULO_SERVICE_INSTANCE}
   fi
 
   case "$service_type" in
@@ -234,10 +296,10 @@ function main() {
           start_service "$service_type" "$service_name" "$@"
           ;;
         stop)
-          stop_service "$service_type" "$service_name" $all_flag
+          stop_service "$service_type" "$service_name" $all_flag "$@"
           ;;
         kill)
-          kill_service "$service_type" "$service_name" $all_flag
+          kill_service "$service_type" "$service_name" $all_flag "$@"
           ;;
         list)
           list_processes "$service_type"
diff --git a/assemble/conf/accumulo-env.sh b/assemble/conf/accumulo-env.sh
index bcde6907be..dc1799c7fc 100644
--- a/assemble/conf/accumulo-env.sh
+++ b/assemble/conf/accumulo-env.sh
@@ -89,23 +89,33 @@ JAVA_OPTS=(
 ## JVM options set for individual applications
 # cmd is set by calling script that sources this env file
 #shellcheck disable=SC2154
-case "$cmd" in
-  manager | master) JAVA_OPTS=('-Xmx512m' '-Xms512m' "${JAVA_OPTS[@]}") ;;
-  monitor) JAVA_OPTS=('-Xmx256m' '-Xms256m' "${JAVA_OPTS[@]}") ;;
-  gc) JAVA_OPTS=('-Xmx256m' '-Xms256m' "${JAVA_OPTS[@]}") ;;
-  tserver) JAVA_OPTS=('-Xmx768m' '-Xms768m' "${JAVA_OPTS[@]}") ;;
-  compaction-coordinator) JAVA_OPTS=('-Xmx512m' '-Xms512m' "${JAVA_OPTS[@]}") 
;;
-  compactor) JAVA_OPTS=('-Xmx256m' '-Xms256m' "${JAVA_OPTS[@]}") ;;
-  sserver) JAVA_OPTS=('-Xmx512m' '-Xms512m' "${JAVA_OPTS[@]}") ;;
-  *) JAVA_OPTS=('-Xmx256m' '-Xms64m' "${JAVA_OPTS[@]}") ;;
+case "${ACCUMULO_RESOURCE_GROUP:-default}" in
+  default)
+    # shellcheck disable=SC2154
+    # $cmd is exported in the accumulo script, but not the accumulo-service 
script
+    case "$cmd" in
+      manager | master) JAVA_OPTS=('-Xmx512m' '-Xms512m' "${JAVA_OPTS[@]}") ;;
+      monitor) JAVA_OPTS=('-Xmx256m' '-Xms256m' "${JAVA_OPTS[@]}") ;;
+      gc) JAVA_OPTS=('-Xmx256m' '-Xms256m' "${JAVA_OPTS[@]}") ;;
+      tserver) JAVA_OPTS=('-Xmx768m' '-Xms768m' "${JAVA_OPTS[@]}") ;;
+      compaction-coordinator) JAVA_OPTS=('-Xmx512m' '-Xms512m' 
"${JAVA_OPTS[@]}") ;;
+      compactor) JAVA_OPTS=('-Xmx256m' '-Xms256m' "${JAVA_OPTS[@]}") ;;
+      sserver) JAVA_OPTS=('-Xmx512m' '-Xms512m' "${JAVA_OPTS[@]}") ;;
+      *) JAVA_OPTS=('-Xmx256m' '-Xms64m' "${JAVA_OPTS[@]}") ;;
+    esac
+    ;;
+  *)
+    echo "ACCUMULO_RESOURCE_GROUP named $ACCUMULO_RESOURCE_GROUP is not 
configured in accumulo-env.sh"
+    exit 1
+    ;;
 esac
 
 ## JVM options set for logging. Review log4j2.properties file to see how they 
are used.
 JAVA_OPTS=("-Daccumulo.log.dir=${ACCUMULO_LOG_DIR}"
-  "-Daccumulo.application=${cmd}${ACCUMULO_SERVICE_INSTANCE}_$(hostname)"
+  "-Daccumulo.application=${ACCUMULO_SERVICE_INSTANCE}_$(hostname)"
   "-Daccumulo.metrics.service.instance=${ACCUMULO_SERVICE_INSTANCE}"
   
"-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
-  "-Dotel.service.name=${cmd}${ACCUMULO_SERVICE_INSTANCE}"
+  "-Dotel.service.name=${ACCUMULO_SERVICE_INSTANCE}"
   "${JAVA_OPTS[@]}"
 )
 
diff --git a/assemble/conf/accumulo.properties 
b/assemble/conf/accumulo.properties
index 8aea483153..6e6de34cc6 100644
--- a/assemble/conf/accumulo.properties
+++ b/assemble/conf/accumulo.properties
@@ -31,3 +31,8 @@ instance.secret=DEFAULT
 
 ## Set to false if 'accumulo-util build-native' fails
 tserver.memory.maps.native.enabled=true
+
+## (optional) include additional property files for a resource group
+## based on the ACCUMULO_RESOURCE_GROUP env var set in accumulo-service
+#include=group-${env:ACCUMULO_RESOURCE_GROUP}.properties
+#includeOptional=group-${env:ACCUMULO_RESOURCE_GROUP}.properties
diff --git 
a/core/src/main/java/org/apache/accumulo/core/conf/cluster/ClusterConfigParser.java
 
b/core/src/main/java/org/apache/accumulo/core/conf/cluster/ClusterConfigParser.java
index dcc3d469e2..ff71848d2f 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/conf/cluster/ClusterConfigParser.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/conf/cluster/ClusterConfigParser.java
@@ -84,14 +84,12 @@ public class ClusterConfigParser {
         (parentKey == null || parentKey.equals("")) ? "" : parentKey + 
addTheDot(parentKey);
     if (value instanceof String) {
       results.put(parent + key, (String) value);
-      return;
     } else if (value instanceof List) {
       ((List<?>) value).forEach(l -> {
         if (l instanceof String) {
           // remove the [] at the ends of toString()
           String val = value.toString();
           results.put(parent + key, val.substring(1, val.length() - 
1).replace(", ", " "));
-          return;
         } else {
           flatten(parent, key, l, results);
         }
@@ -102,9 +100,8 @@ public class ClusterConfigParser {
       map.forEach((k, v) -> flatten(parent + key, k, v, results));
     } else if (value instanceof Number) {
       results.put(parent + key, value.toString());
-      return;
     } else {
-      throw new RuntimeException("Unhandled object type: " + value.getClass());
+      throw new IllegalStateException("Unhandled object type: " + 
value.getClass());
     }
   }
 
@@ -121,7 +118,7 @@ public class ClusterConfigParser {
         out.printf(PROPERTY_FORMAT, section.toUpperCase() + "_HOSTS", 
config.get(section));
       } else {
         if (section.equals("manager") || section.equals("tserver")) {
-          throw new RuntimeException("Required configuration section is 
missing: " + section);
+          throw new IllegalStateException("Required configuration section is 
missing: " + section);
         }
         System.err.println("WARN: " + section + " is missing");
       }

Reply via email to