#!/bin/bash -u
# This script will assist to setup Galera cluster ProxySQL monitoring script.
#####################################################################################


#-------------------------------------------------------------------------------
#
# Step 1 : Bash internal configuration
#

set -o nounset    # no undefined variables
set -o pipefail   # internal pipe failures cause an exit

#bash prompt internal configuration
declare RED=""
declare NRED=""

#-------------------------------------------------------------------------------
#
# Step 2 : Global variables
#

declare -i  DEBUG=0
readonly    PROXYSQL_ADMIN_VERSION="2.0.16"

declare     CONFIG_FILE="/etc/proxysql-admin.cnf"
declare     ERR_FILE="/dev/null"
declare     RELOAD_CHECK_FILE=""

# Set to send output here when DEBUG is set
declare     DEBUG_ERR_FILE="/dev/null"

declare -i  WRITE_HOSTGROUP_ID=1
declare -i  READ_HOSTGROUP_ID=-1
declare -i  SLAVEREAD_HOSTGROUP_ID

# This is the hostgroup that new nodes will be added to
declare -i  DEFAULT_HOSTGROUP_ID

declare     MODE

declare     CHECK_STATUS=0

declare     PROXYSQL_DATADIR='/var/lib/proxysql'

declare -i  TIMEOUT=10

# Maximum time to wait for cluster status
declare -i  CLUSTER_TIMEOUT=3

# Extra text that will be logged with the output
# (useful for debugging/testing)
declare     LOG_TEXT=""

# Default value for max_connections in mysql_servers
declare     MAX_CONNECTIONS="1000"


#we need to be able to disable autodiscovery
#This because in presence of multiple IPs on the PXC nodes the script choose to use the use the one
#Nodes are registered for internal traffic which is normally different from the one
#used for application traffic.
#Result is that the AUTODICOVERY will cause ProxySQL to use wrong IP set
declare PXC_AUTODISCOVERY=1

#-------------------------------------------------------------------------------
#
# Step 3 : Helper functions
#

function log() {
  local lineno=$1
  shift

  if [[ -n $ERR_FILE ]]; then
    if [[ -n $lineno && $DEBUG -ne 0 ]]; then
      echo "[$(date +%Y-%m-%d\ %H:%M:%S)] (line $lineno) $*" >> $ERR_FILE
    else
      echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*" >> $ERR_FILE
    fi
  fi
}

function log_if_success() {
  local lineno=$1
  local rc=$2
  shift 2

  if [[ $rc -eq 0 ]]; then
    log "$lineno" "$*"
  fi
}

function error() {
  local lineno=$1
  shift

  log "$lineno" "ERROR: $*"
}

function warning() {
  local lineno=$1
  shift

  log "$lineno" "WARNING: $*"
}

function debug() {
  if [[ $DEBUG -eq 0 ]]; then
    return
  fi

  local lineno=$1
  shift

  log "$lineno" "${RED}debug: $*${NRED}"
}


function usage () {
  local path=$0
  cat << EOF
Usage: ${path##*/} [ options ]

Example:
  proxysql_node_monitor --write-hg=10 --read-hg=11 --config-file=/etc/proxysql-admin.cnf --log=/var/lib/proxysql/pxc_test_proxysql_galera_check.log

Options:
  -w, --write-hg=<NUMBER>             Specify ProxySQL write hostgroup.
  -r, --read-hg=<NUMBER>              Specify ProxySQL read hostgroup.
  -m, --mode=[loadbal|singlewrite]    ProxySQL read/write configuration mode, currently supporting: 'loadbal' and 'singlewrite' (the default) modes
  -p, --priority=<HOST_LIST>          Can accept comma delimited list of write nodes priority
  -c, --config-file=PATH              Specify ProxySQL-admin configuration file.
  -l, --log=PATH                      Specify proxysql_node_monitor log file.
  --log-text=TEXT                     This is text that will be written to the log file
                                      whenever this script is run (useful for debugging).
  --reload-check-file=PATH            Specify file used to notify proysql_galera_checker
                                      of a change in server configuration
  --max-connections=<NUMBER>          Value for max_connections in the mysql_servers table.
                                      This is the maximum number of connections that
                                      ProxySQL will open to the backend servers.
                                      (default: 1000)
  --debug                             Enables additional debug logging.
  -h, --help                          Display script usage information
  -v, --version                       Print version info
EOF
}


# Check the permissions for a file or directory
#
# Globals:
#   None
#
# Arguments:
#   1: the bash test to be applied to the file
#   2: the lineno where this call is invoked (used for errors)
#   3: the path to the file
#   4: (optional) description of the path (mostly used for existence checks)
#
# Exits the script if the permissions test fails.
#
function check_permission() {
  local permission=$1
  local lineno=$2
  local path_to_check=$3
  local description=""
  if [[ $# -gt 3 ]]; then
    description="$4"
  fi

  if [ ! $permission "$path_to_check" ] ; then
    if [[ $permission == "-r" ]]; then
      error $lineno "You do not have READ permission for: $path_to_check"
    elif [[ $permission == "-w" ]]; then
      error $lineno "You do not have WRITE permission for: $path_to_check"
    elif [[ $permission == "-x" ]]; then
      error $lineno "You do not have EXECUTE permission for: $path_to_check"
    elif [[ $permission == "-e" ]]; then
      if [[ -n $description ]]; then
        error $lineno "Could not find the $description: $path_to_check"
      else
        error $lineno "Could not find: $path_to_check"
      fi
    elif [[ $permission == "-d" ]]; then
      if [[ -n $description ]]; then
        error $lineno "Could not find the $description: $path_to_check"
      else
        error $lineno "Could not find the directory: $path_to_check"
      fi
    elif [[ $permission == "-f" ]]; then
      if [[ -n $description ]]; then
        error $lineno "Could not find the $description: $path_to_check"
      else
        error $lineno "Could not find the file: $path_to_check"
      fi
    else
      error $lineno "You do not have the correct permissions for: $path_to_check"
    fi
    exit 1
  fi
}


#
#
# Globals:
#   None
#
# Arguments:
#   1: the lineno
#   2: the return value that is being checked
#   3: the error message
#   4: Additional information (only used if an error occurred) (optional)
#
# Returns:
#   Returns the return value that is passed in.
#   This allows the code that follows to check the return value.
#
# Note that this will NOT exit the script.
#
function check_cmd() {
  local lineno=$1
  local mpid=$2
  local error_msg=$3
  local error_info=""

  if [[ $# -ge 4 ]]; then
    error_info=$4
  fi

  if [ "$mpid" == "124" ]; then
    error $lineno "TIMEOUT: Connection terminated due to timeout."
  fi
  if [ ${mpid} -ne 0 ]; then
    warning $lineno "$error_msg."
    if [[ ! -z  $error_info ]]; then
      log $lineno "$error_info."
    fi
  fi
  return $mpid
}

# Executes a SQL query with the (fully) specified server
#
# Globals:
#   None
#
# Arguments:
#   1: lineno
#   2: the name of the user
#   3: the user's password
#   4: the hostname of the server
#   5: the port used to connect to the server
#   6: timeout in secs
#   7: arguments to the mysql client
#   8: additional options to the [client] config
#   9: the query to be run
#   10: additional options, space separated
#      Available options:
#       "hide_output"
#         This will not show the output of the query when DEBUG is set.
#         Used to stop the display of sensitve information (such as passwords)
#         from being displayed when debugging.
#
function exec_sql() {
  local lineno=$1
  local user=$2
  local password=$3
  local hostname=$4
  local port=$5
  local timeout_secs=$6
  local args=$7
  local client_options=$8
  local query="$9"
  local more_options="${10}"
  local retvalue
  local retoutput

  debug "$lineno" "exec_sql : $user@$hostname:$port ==> $query"

  retoutput=$(printf "[client]\n${client_options}\nuser=${user}\npassword=\"${password}\"\nhost=${hostname}\nport=${port}"  \
      | timeout ${timeout_secs} mysql --defaults-file=/dev/stdin --protocol=tcp \
              ${args} -e "$query")
  retvalue=$?

  if [[ $DEBUG -eq 1 ]]; then
    local number_of_newlines=0
    local dbgoutput=$retoutput

    if [[ " $more_options " =~ [[:space:]]hide_output[[:space:]] ]]; then
      dbgoutput="**** data hidden ****"
    fi

    if [[ -n $dbgoutput ]]; then
      number_of_newlines=$(printf "%s" "${dbgoutput}" | wc -l)
    fi

    if [[  $retvalue -ne 0 ]]; then
      debug "" "--> query failed $retvalue"
    elif [[ -z $dbgoutput ]]; then
      debug "" "--> query returned $retvalue : <query returned no data>"
    elif [[ ${number_of_newlines} -eq 0 ]]; then
      debug "" "--> query returned $retvalue : ${dbgoutput}"
    else
      debug "" "--> query returned $retvalue : <data follows>"
      printf "${dbgoutput//%/%%}\n" | while IFS= read -r line; do
        debug "" "----> $line"
      done
    fi
  fi

  printf "${retoutput//%/%%}"
  return $retvalue
}


# Executes a SQL query on proxysql (with a timeout of $TIMEOUT seconds)
#
# Globals:
#   PROXYSQL_USERNAME
#   PROXYSQL_PASSWORD
#   PROXYSQL_HOSTNAME
#   PROXYSQL_PORT
#   TIMEOUT
#
# Arguments:
#   1: lineno (used for debugging/output, may be blank)
#   2: The SQL query
#   3: (optional) more options, see exec_sql
#
function proxysql_exec() {
  local lineno=$1
  local query="$2"
  local more_options=""
  local retoutput

  if [[ $# -ge 3 ]]; then
    more_options=$3
  fi

  exec_sql "$lineno" "$PROXYSQL_USERNAME" "$PROXYSQL_PASSWORD" \
           "$PROXYSQL_HOSTNAME" "$PROXYSQL_PORT" \
           "$TIMEOUT" "-Bs" "" "$query" "$more_options"
  retoutput=$?
  return $retoutput
}

# Executes a SQL query on mysql (with a timeout of $TIMEOUT secs)
#
# Globals:
#   CLUSTER_USERNAME
#   CLUSTER_PASSWORD
#   CLUSTER_HOSTNAME
#   CLUSTER_PORT
#   CLUSTER_TIMEOUT
#
# Arguments:
#   1: lineno (used for debugging/output, may be blank)
#   2: the query to be run
#   3: (optional) more options, see exec_sql
#
function mysql_exec() {
  local lineno=$1
  local query=$2
  local more_options=""
  local retoutput

  if [[ $# -ge 3 ]]; then
    more_options=$3
  fi

  exec_sql "$lineno" "$CLUSTER_USERNAME" "$CLUSTER_PASSWORD" \
           "$CLUSTER_HOSTNAME" "$CLUSTER_PORT" \
           "$TIMEOUT" "-Bs" "connect-timeout=${CLUSTER_TIMEOUT}" "$query" "$more_options"
  retoutput=$?
  return $retoutput
}


# Executes a SQL query on mysql (with a timeout of $TIMEOUT secs)
#
# Globals:
#   CLUSTER_USERNAME
#   CLUSTER_PASSWORD
#   CLUSTER_TIMEOUT
#
# Arguments:
#   1: lineno (used for debugging/output, may be blank)
#   2: the hostname of the server
#   3: the port used to connect to the server
#   4: the query to be run
#   5: (optional) more options, see exec_sql
#
function slave_exec() {
  local lineno=$1
  local hostname=$2
  local port=$3
  local query=$4
  local more_options=""
  local timeout_secs=$TIMEOUT
  local retoutput

  if [[ $# -ge 5 ]]; then
    more_options=$5
  fi


  exec_sql "$lineno" "$CLUSTER_USERNAME" "$CLUSTER_PASSWORD" \
           "$hostname" "$port" \
           "$timeout_secs" "-Bs" "" "$query" "$more_options"
  retoutput=$?
  return $retoutput
}

# Separates the IP address from the port in a network address
# Works for IPv4 and IPv6
#
# Globals:
#   None
#
# Params:
#   1. The network address to be parsed
#
# Outputs:
#   A string with a space separating the IP address from the port
#
function separate_ip_port_from_address()
{
  #
  # Break address string into host:port/path parts
  #
  local address=$1

  # Has to have at least one ':' to separate the port from the ip address
  if [[ $address =~ : ]]; then
    ip_addr=${address%:*}
    port=${address##*:}
  else
    ip_addr=$address
    port=""
  fi

  # Remove any braces that surround the ip address portion
  ip_addr=${ip_addr#\[}
  ip_addr=${ip_addr%\]}

  echo "${ip_addr} ${port}"
}

# Combines the IP address and port into a network address
# Works for IPv4 and IPv6
# (If the IP address is IPv6, the IP portion will have brackets)
#
# Globals:
#   None
#
# Params:
#   1: The IP address portion
#   2: The port
#
# Outputs:
#   A string containing the full network address
#
function combine_ip_port_into_address()
{
  local ip_addr=$1
  local port=$2
  local addr

  if [[ ! $ip_addr =~ \[.*\] && $ip_addr =~ .*:.* ]] ; then
    # If there are no brackets and it does have a ':', then add the brackets
    # because this is an unbracketed IPv6 address
    addr="[${ip_addr}]:${port}"
  else
    addr="${ip_addr}:${port}"
  fi
  echo $addr
}


# Update Galera Cluster nodes in ProxySQL database
# This will take care of nodes that have gone up or gone down
# (i.e. if the ProxySQL and PXC memberships differ).
#
# This does not take care of the policy issues, it does not
# ensure there is a writer.
#
# Globals:
#   WRITE_HOSTGROUP_ID
#   READ_HOSTGROUP_ID
#   SLAVEREAD_HOSTGROUP_ID
#   MODE
#   MODE_COMMENT
#   CHECK_STATUS
#
# Arguments:
#   1: active cluster host (may be empty if cluster is offline)
#   1: active cluster port (may be empty if cluster is offline)
#
function update_cluster() {
  debug $LINENO "START update_cluster"
  local cluster_host=$1
  local cluster_port=$2
  local host_info=""
  local current_hosts=""
  local is_current_hosts_empty=0
  local wsrep_address=""
  local ws_address
  local ws_ip
  local ws_port
  local ws_hg_status
  local ws_hg_id
  local ws_status
  local ws_comment

  # get all nodes from ProxySQL in use by hostgroups
  host_info=$(proxysql_exec $LINENO "SELECT DISTINCT hostname || ':' || port,hostgroup_id,status FROM mysql_servers where status != 'OFFLINE_HARD' and hostgroup_id in ( $WRITE_HOSTGROUP_ID, $READ_HOSTGROUP_ID, $SLAVEREAD_HOSTGROUP_ID )" | tr '\t' ' ')
  if [[ -n host_info ]]; then
    # Extract the hostname and port from the rows
    # Creates a string of "host:port" separated by spaces
    current_hosts=""

    while read line; do
      if [[ -z $line ]]; then
        continue
      fi
      net_address=$(echo $line | cut -d' ' -f1)
      net_address=$(separate_ip_port_from_address $net_address)
      local ip_addr=$(echo "$net_address" | cut -d' ' -f1)
      local port=$(echo "$net_address" | cut -d' ' -f2)
      net_address=$(combine_ip_port_into_address "$ip_addr" "$port")
      current_hosts+="$net_address "
    done< <(printf "$host_info\n")

    current_hosts=${current_hosts% }
  fi

  if [[ -n $cluster_host && -n $cluster_port ]]; then
    # First, find a host that is online from ProxySQL
    ws_ip=$cluster_host
    ws_port=$cluster_port

    # Second, get the wsrep_incoming_addresses from the cluster
    wsrep_address=$(slave_exec $LINENO "${ws_ip}" "${ws_port}" \
          "SHOW STATUS LIKE 'wsrep_incoming_addresses'" | awk '{print $2}' | sed 's|,| |g')
  fi

  if [[ -z $wsrep_address && -z $current_hosts ]]; then
    debug $LINENO "Returning from update_cluster(), both PXC and ProxySQL have no active nodes"
    return
  fi

  #
  # Given the WSREP members, compare to ProxySQL
  # If missing from ProxySQL, add to ProxySQL as a reader.
  #
  debug $LINENO "Looking for PXC nodes not in ProxySQL"
  for i in ${wsrep_address}; do
    # if we have a match, the the PXC node is in ProxySQL and we can skip
    if [[ -n $current_hosts && " ${current_hosts} " =~ " ${i} " ]]; then
      continue
    fi

    log $LINENO "Cluster node (${i}) does not exist in ProxySQL, adding as a $MODE_COMMENT node"
    ws_address=$(separate_ip_port_from_address "$i")
    ws_ip=$(echo "$ws_address" | cut -d' ' -f1)
    ws_port=$(echo "$ws_address" | cut -d' ' -f2)

    # Add the node as a reader
    local hostgroup

    # Before inserting, check if a previous READ entry exists (it may be in OFFLINE_HARD state)
    hostgroup=$(proxysql_exec $LINENO "SELECT hostgroup_id FROM mysql_servers WHERE hostgroup_id=${DEFAULT_HOSTGROUP_ID} AND hostname='${ws_ip}' AND port=${ws_port}")

    if [[ -n $hostgroup ]]; then
      # Update reader to OFFLINE_SOFT if new PXC node in ProxySQL
      proxysql_exec $LINENO "UPDATE mysql_servers SET status='OFFLINE_SOFT',weight=1000,comment='$MODE_COMMENT' WHERE hostname='${ws_ip}' AND port=${ws_port} AND hostgroup_id=${hostgroup}"
      check_cmd $LINENO $? "Cannot update Galera Cluster node $ws_address (hostgroup $hostgroup) to ProxySQL database, Please check ProxySQL login credentials"
      log_if_success $LINENO $? "Updated ${hostgroup}:${i} node in the ProxySQL database."
    else
      if [[ $PXC_AUTODISCOVERY -eq 1 ]]; then
          # Insert a reader if new PXC node not in ProxySQL
          proxysql_exec $LINENO "INSERT INTO mysql_servers (hostname,hostgroup_id,port,weight,comment,max_connections) VALUES ('$ws_ip',$DEFAULT_HOSTGROUP_ID,$ws_port,1000,'$MODE_COMMENT',$MAX_CONNECTIONS);"
          check_cmd $LINENO $? "Cannot add Galera Cluster node $ws_address (hostgroup $DEFAULT_HOSTGROUP_ID) to ProxySQL database, Please check ProxySQL login credentials"
          log_if_success $LINENO $? "Added ${DEFAULT_HOSTGROUP_ID}:${i} node into ProxySQL database."
      else
          log_if_success $LINENO $? "Node identified by AUTODISCOVERY but will not add it because insert by AUTODISCOVERY for PXC is disable. Candidate node: ${DEFAULT_HOSTGROUP_ID}:${i} for ProxySQL database."
      fi
    fi

    CHECK_STATUS=1
  done

  #
  # Given the ProxySQL members, compare to WSREP
  # If not in WSREP, mark as OFFLINE_HARD
  #
  debug $LINENO "Looking for ProxySQL nodes not in PXC"
  for i in $current_hosts; do
    # if we have a match, then the proxysql node is in PXC
    # so we can skip it
    if [[ -n ${wsrep_address} && " ${wsrep_address} " =~ " ${i} " ]]; then
      continue
    fi

    debug $LINENO "ProxySQL host $i not found in cluster membership"
    #
    # The current host in current_hosts was not found in cluster membership,
    # set it OFFLINE_SOFT unless its a slave node
    #
    ws_address=$(separate_ip_port_from_address "$i")
    ws_ip=$(echo "$ws_address" | cut -d' ' -f1)
    ws_port=$(echo "$ws_address" | cut -d' ' -f2)

    # This is supported by status, so OFFLINE should come before ONLINE
    # Note that the status is in DESC order, so "ONLINE : OFFLINE_SOFT : OFFLINE_HARD"
    # This is needed because there may be multiple entries
    ws_hg_status=$(proxysql_exec $LINENO "SELECT hostgroup_id,status,comment from mysql_servers WHERE hostname='$ws_ip' and port=$ws_port ORDER BY status DESC LIMIT 1")
    ws_hg_id=$(echo -e "$ws_hg_status" | cut -f1)
    ws_status=$(echo -e "$ws_hg_status" | cut -f2)
    ws_comment=$(echo -e "$ws_hg_status" | cut -f3)

    if [ "$ws_comment" == "SLAVEREAD" ]; then
      # This update now happens in proxysql_galera_checker
      continue
    fi

    if [ "$ws_status" == "OFFLINE_SOFT" ]; then
      #
      # If OFFLINE_SOFT, move to OFFLINE_HARD
      #

      log $LINENO "Cluster node ${ws_hg_id}:${i} does not exist in PXC! Changing status from OFFLINE_SOFT to OFFLINE_HARD"
      proxysql_exec $LINENO "UPDATE mysql_servers set status='OFFLINE_HARD' WHERE hostname='$ws_ip' and port=$ws_port"
      check_cmd $LINENO $? "Cannot update Galera Cluster writer node in ProxySQL database, Please check ProxySQL login credentials"
      CHECK_STATUS=1
    elif [[ $ws_status == "ONLINE" ]]; then
      #
      # else if ONLINE, move to OFFLINE_SOFT
      # It will take another iteration to get it to OFFLINE_HARD
      #
      log $LINENO "Cluster node ${ws_hg_id}:${i} does not exist in PXC! Changing status to OFFLINE_SOFT"
      # Set all entries to OFFLINE_SOFT
      proxysql_exec $LINENO "UPDATE mysql_servers set status='OFFLINE_SOFT' WHERE hostname='$ws_ip' and port=$ws_port"
      check_cmd $LINENO $? "Cannot update Galera Cluster writer node in ProxySQL database, Please check ProxySQL login credentials"
      CHECK_STATUS=1
    fi

    node_status=$(proxysql_exec $LINENO "SELECT status from mysql_servers WHERE hostname='$ws_ip' and port=$ws_port ORDER BY status LIMIT 1")
    log $LINENO "Non-PXC node (${i}) current status '$node_status' in ProxySQL."
  done

  # Update the ProxySQL status for the new nodes
  for i in ${wsrep_address}; do
    if [[ -n $current_hosts && " ${current_hosts} " =~ " ${i} " ]]; then
      # Lookup the status in the host_info
      local host

      ws_address=$(separate_ip_port_from_address "$i")
      ws_ip=$(echo "$ws_address" | cut -d' ' -f1)
      ws_port=$(echo "$ws_address" | cut -d' ' -f2)

      # properly escape the characters for grep
      local re_i="$(printf '%s' "$ws_ip:$ws_port" | sed 's/[.[\*^$]/\\&/g')"
      host=$(echo "$host_info" | grep "${re_i}" | head -1)

      ws_hg_id=$(echo $host | cut -d' ' -f2)
      ws_status=$(echo $host | cut -d' ' -f3)
      log "" "Cluster node (${ws_hg_id}:${i}) current status '$ws_status' in ProxySQL."
    else
      ws_address=$(separate_ip_port_from_address "$i")
      ws_ip=$(echo "$ws_address" | cut -d' ' -f1)
      ws_port=$(echo "$ws_address" | cut -d' ' -f2)
      ws_hg_status=$(proxysql_exec $LINENO "SELECT hostgroup_id,status from mysql_servers WHERE hostname='$ws_ip' and port=$ws_port")
      ws_hg_id=$(echo $ws_hg_status | cut -d' ' -f1)
      ws_status=$(echo $ws_hg_status | cut -d' ' -f2)

      log $LINENO "Cluster node (${ws_hg_id}:${i}) current status '$ws_status' in ProxySQL database!"
      if [ "$ws_status" == "OFFLINE_HARD" ]; then
        # The node was OFFLINE_HARD, but its now in the cluster list
        # so lets make it OFFLINE_SOFT
        proxysql_exec $LINENO "UPDATE mysql_servers set status = 'OFFLINE_SOFT' WHERE hostname='$ws_ip' and port=$ws_port;"
        check_cmd $LINENO $? "Cannot update Galera Cluster node $i in the ProxySQL database, Please check the ProxySQL login credentials"
        log_if_success $LINENO $? "${ws_hg_id}:${i} node set to OFFLINE_SOFT status to ProxySQL database."
        CHECK_STATUS=1
      fi
    fi
  done
  debug $LINENO "END update_cluster"
}


# Move the entries in the list from writers to readers
#
# Globals:
#   READ_HOSTGROUP_ID
#   WRITE_HOSTGROUP_ID
#   CHECK_STATUS
#
# Arguments:
#   1: A list of nodes to move to readers (entries are 'server port hostgroup')
#
function move_writers_to_readers() {
  debug $LINENO "START move_writers_to_readers($*)"
  local offline_writers=$1

  debug $LINENO "$offline_writers"
  printf "$offline_writers" | while read host port hostgroup status || [ -n "$hostgroup" ]
  do
    local read_count

    debug $LINENO "mode_change_check: Found OFFLINE_SOFT writer, changing to READ status and hostgroup $READ_HOSTGROUP_ID"

    read_count=$(proxysql_exec $LINENO "SELECT COUNT(*) FROM mysql_servers WHERE hostgroup_id=$READ_HOSTGROUP_ID AND hostname='$host' AND port=$port")
    if [[ $read_count -ne 0 ]]; then
      # If node is already a READER, update the READER
      if [[ $status == 'ONLINE' ]]; then
        proxysql_exec $LINENO "UPDATE mysql_servers SET status='OFFLINE_SOFT',hostgroup_id=$READ_HOSTGROUP_ID, comment='READ', weight=1000 WHERE hostgroup_id=$READ_HOSTGROUP_ID AND hostname='$host' AND port=$port"
        check_cmd $LINENO $? "Cannot update Galera Cluster writer node in ProxySQL database, Please check ProxySQL login credentials"
        log_if_success $LINENO $? "Changed OFFLINE_SOFT writer to a reader ($READ_HOSTGROUP_ID:$host:$port)"
        CHECK_STATUS=1
      fi
    else
      if [[ $status == "ONLINE" ]]; then
        local address
        address=$(combine_ip_port_into_address "$host" "$port")

        # If node is not a reader, add node as a reader
        debug $LINENO "Node is not a reader, adding node as a reader"

        proxysql_exec $LINENO \
              "INSERT INTO mysql_servers (hostname,hostgroup_id,port,status,weight,comment,max_connections)
                  VALUES ('$host',$READ_HOSTGROUP_ID,$port,'OFFLINE_SOFT',1000,'READ',$MAX_CONNECTIONS);"
        check_cmd $LINENO $? "Cannot add Galera Cluster reader node in ProxySQL database, Please check ProxySQL login credentials"
        log $LINENO "Adding server $READ_HOSTGROUP_ID:$address with status OFFLINE_SOFT."
        CHECK_STATUS=1
      fi
    fi
  done
}


#
# Globals:
#   PROXYSQL_DATADIR
#   CLUSTER_NAME
#   WRITE_HOSTGROUP_ID  READ_HOSTGROUP_ID
#   MODE
#
# Arguments:
#   None
#
function mode_change_check(){
  debug $LINENO "START mode_change_check"

  # Check if the current writer is in an OFFLINE_SOFT state
  local offline_writers
  offline_writers=$(proxysql_exec $LINENO "SELECT hostname,port,hostgroup_id,status from mysql_servers where comment in ('WRITE', 'READWRITE') and status <> 'ONLINE' and hostgroup_id in ($WRITE_HOSTGROUP_ID)")
  if [[ -n $offline_writers ]]; then
    #
    # Found a writer node that was in 'OFFLINE_SOFT' state,
    # move it to the READ hostgroup unless the MODE is 'loadbal'
    #
    if [ "$MODE" != "loadbal" ]; then
      move_writers_to_readers "$offline_writers"
    fi
  fi

  debug $LINENO "END mode_change_check"
}


#
# Globals:
#   DEBUG
#   CONFIG_FILE
#   WRITE_HOSTGROUP_ID  READ_HOSTGROUP_ID
#   DEFAULT_HOSTGROUP_ID
#   MODE
#   ERR_FILE
#   PROXYSQL_ADMIN_VERSION
#   MODE_COMMENT
#   WRITE_WEIGHT
#
# Arguments:
#
function parse_args() {
  # Check if we have a functional getopt(1)
  if ! getopt --test; then
    go_out="$(getopt --options=w:r:c:l:m:p:vh --longoptions=write-hg:,read-hg:,mode:,priority:,config-file:,log:,reload-check-file:,log-text:,max-connections:,debug,version,help \
    --name="$(basename "$0")" -- "$@")"
    if [[ $? -ne 0 ]]; then
      # no place to send output
      echo "Script error: getopt() failed" >&2
      exit 1
    fi
    eval set -- "$go_out"
  fi

  if [[ $go_out == " --" ]];then
    usage
    exit 1
  fi

  #
  # We iterate through the command-line options twice
  # (1) to handle options that don't need permissions (such as --help)
  # (2) to handle options that need to be done before other
  #     options, such as loading the config file
  #
  for arg
  do
    case "$arg" in
      -- ) shift; break;;
      --config-file )
        CONFIG_FILE="$2"
        check_permission -e $LINENO "$CONFIG_FILE" "proxysql-admin configuration file"
        debug $LINENO  "--config-file specified, using : $CONFIG_FILE"
        shift 2
        ;;
      --help)
        usage
        exit 0
        ;;
      -v | --version)
        echo "proxysql_node_monitor version $PROXYSQL_ADMIN_VERSION"
        exit 0
        ;;
      --debug)
        DEBUG=1
        shift
        ;;
      *)
        shift
        ;;
    esac
  done

  #
  # Load the config file before reading in the command-line options
  #
  readonly CONFIG_FILE
  if [ ! -e "$CONFIG_FILE" ]; then
      warning "" "Could not locate the configuration file: $CONFIG_FILE"
  else
      check_permission -r $LINENO "$CONFIG_FILE"
      debug $LINENO "Loading $CONFIG_FILE"
      source "$CONFIG_FILE"
  fi


  if [[ $DEBUG -ne 0 ]]; then
    # For now
    if [[ -t 1 ]]; then
      ERR_FILE=/dev/stdout
    fi
  fi

  local p_mode=""

  # Reset the command line for the next invocation
  eval set -- "$go_out"

  for arg
  do
    case "$arg" in
      -- ) shift; break;;
      -w | --write-hg )
        WRITE_HOSTGROUP_ID=$2
        shift 2
      ;;
      -r | --read-hg )
        READ_HOSTGROUP_ID=$2
        shift 2
      ;;
      -m | --mode )
        p_mode="$2"
        shift 2
        if [ "$p_mode" != "loadbal" ] && [ "$p_mode" != "singlewrite" ]; then
          echo "ERROR: Invalid --mode passed:"
          echo "  Please choose any of these modes: loadbal, singlewrite"
          exit 1
        fi
      ;;
      -p | --priority )
        # old parameter
        shift 2
      ;;
      --config-file )
        shift 2
        # The config-file is loaded before the command-line
        # arguments are handled.
      ;;
      -l | --log )
        ERR_FILE="$2"
        shift 2

        # Test if stdout and stderr are open to a terminal
        if [[ $ERR_FILE == "/dev/stdout" || $ERR_FILE == "/dev/stderr" ]]; then
          RED=$(tput setaf 1)
          NRED=$(tput sgr0)
        fi
      ;;
      --reload-check-file )
        RELOAD_CHECK_FILE="$2"
        shift 2
      ;;
      --log-text )
        LOG_TEXT="$2"
        shift 2
      ;;
      --max-connections )
        MAX_CONNECTIONS="$2"
        shift 2
      ;;
      --debug )
        shift;
      ;;
      -v | --version )
        shift;
      ;;
      -h | --help )
        shift;
      ;;
    esac
  done

  if [[ $DEBUG -eq 1 ]]; then
    DEBUG_ERR_FILE=$ERR_FILE
  fi

  #Timeout exists for instances where mysqld/proxysql may be hung
  TIMEOUT=5

  SLAVEREAD_HOSTGROUP_ID=$READ_HOSTGROUP_ID
  if [ $SLAVEREAD_HOSTGROUP_ID -eq $WRITE_HOSTGROUP_ID ];then
    let SLAVEREAD_HOSTGROUP_ID+=1
  fi

  DEFAULT_HOSTGROUP_ID=$READ_HOSTGROUP_ID
  if [[ $DEFAULT_HOSTGROUP_ID -eq -1 ]]; then
    DEFAULT_HOSTGROUP_ID=$WRITE_HOSTGROUP_ID
  fi

  CHECK_STATUS=0

  debug $LINENO "#### PROXYSQL NODE MONITOR ARGUMENT CHECKING"
  debug $LINENO "MODE: $MODE"
  debug $LINENO "check mode name from proxysql data directory "
  CLUSTER_NAME=$(proxysql_exec $LINENO "SELECT comment from scheduler where arg1 LIKE '%--write-hg=$WRITE_HOSTGROUP_ID %' OR arg1 LIKE '%-w $WRITE_HOSTGROUP_ID %'")
  check_cmd $LINENO $? "Cannot connect to ProxySQL at $PROXYSQL_HOSTNAME:$PROXYSQL_PORT"
  if [[ ! -z $p_mode ]] ; then
    MODE=$p_mode
    debug $LINENO "command-line: setting MODE to $MODE"
  else
    # Get the name of the mode file
    local proxysql_mode_file
    if [[ -z $CLUSTER_NAME ]]; then
      proxysql_mode_file="${PROXYSQL_DATADIR}/mode"
    else
      proxysql_mode_file="${PROXYSQL_DATADIR}/${CLUSTER_NAME}_mode"
    fi

    if [[ -f "$proxysql_mode_file" && -r "$proxysql_mode_file" ]]; then
      MODE=$(cat ${proxysql_mode_file})
      debug $LINENO "file: $proxysql_mode_file: setting MODE to $MODE"
    fi
  fi


  if [ "$MODE" == "loadbal" ]; then
    MODE_COMMENT="READWRITE"
    WRITE_WEIGHT="1000"
  else
    MODE_COMMENT="READ"
    WRITE_WEIGHT="1000000"
  fi

  if [[ -z $RELOAD_CHECK_FILE ]]; then
    error $LINENO "The --reload-check-file option is required."
    exit 1
  fi
  check_permission -r $LINENO "$RELOAD_CHECK_FILE"

  # Verify that we have an integer
  if ! [ "$MAX_CONNECTIONS" -eq "$MAX_CONNECTIONS" ] 2>/dev/null
  then
    error $LINENO "Invalid --max-connections value (must be a number) : $MAX_CONNECTIONS"
    exit 1
  fi

  readonly WRITE_HOSTGROUP_ID
  readonly READ_HOSTGROUP_ID
  readonly SLAVEREAD_HOSTGROUP_ID
  readonly MODE
  readonly MODE_COMMENT
  readonly WRITE_WEIGHT
  readonly CLUSTER_NAME
  readonly RELOAD_CHECK_FILE
  readonly MAX_CONNECTIONS
}

# Returns the address of an available (online) cluster host
#
# Globals:
#   WRITE_HOSTGROUP_ID
#   READ_HOSTGROUP_ID
#
# Arguments:
#   None
#
function find_online_cluster_host() {
  # Query the proxysql database for hosts,ports in use
  # Then just go through the list until we reach one that responds
  local hosts
  hosts=$(proxysql_exec $LINENO "SELECT DISTINCT hostname,port FROM mysql_servers WHERE comment<>'SLAVEREAD' AND hostgroup_id in ($WRITE_HOSTGROUP_ID, $READ_HOSTGROUP_ID)")
  printf "$hosts" | while read server port || [[ -n $port ]]
  do
    debug $LINENO "Trying to contact $server:$port..."
    slave_exec "$LINENO" "$server" "$port" "select @@port" 1>/dev/null 2>>${DEBUG_ERR_FILE}
    if [[ $? -eq 0 ]]; then
      printf "$server $port"
      return 0
    fi
  done

  # No cluster host available (cannot contact any)
  return 1
}

function main() {
  # Monitoring user needs 'REPLICATION CLIENT' privilege
  log $LINENO "###### Galera Cluster status ######"
  if [[ -n $LOG_TEXT ]]; then
    log $LINENO "Extra notes        : $LOG_TEXT"
  fi
  debug $LINENO "write hostgroup id : $WRITE_HOSTGROUP_ID"
  debug $LINENO "read hostgroup id  : $READ_HOSTGROUP_ID"
  debug $LINENO "mode               : $MODE"

  CLUSTER_USERNAME=$(proxysql_exec $LINENO "SELECT variable_value FROM global_variables WHERE variable_name='mysql-monitor_username'")
  check_cmd $LINENO $? "Could not retrieve cluster login info from ProxySQL. Please check ProxySQL login credentials"

  CLUSTER_PASSWORD=$(proxysql_exec $LINENO "SELECT variable_value FROM global_variables WHERE variable_name='mysql-monitor_password'" "hide_output")
  check_cmd $LINENO $? "Could not retrieve cluster login info from ProxySQL. Please check ProxySQL login credentials"

  CLUSTER_TIMEOUT=$(proxysql_exec $LINENO "SELECT MAX(MAX(interval_ms / 1000 - 1, 1)) FROM scheduler")

  local cluster_host_info
  cluster_host_info=$(find_online_cluster_host)

  local host=""
  local port=""
  if [[ -n $cluster_host_info ]]; then
    host=$(echo $cluster_host_info | awk '{ print $1 }')
    port=$(echo $cluster_host_info | awk '{ print $2 }')
  fi

  update_cluster "$host" "$port"
  mode_change_check

  if [ $CHECK_STATUS -eq 0 ]; then
    if [[ -n $cluster_host_info ]]; then
      log $LINENO "Galera Cluster membership looks good"
    else
      log $LINENO "Galera Cluster is offline!"
    fi
  else
    echo "1" > ${RELOAD_CHECK_FILE}
    log $LINENO "###### MYSQL SERVERS was updated ######"
  fi
}


#-------------------------------------------------------------------------------
#
# Step 4 : Begin script execution
#

parse_args "$@"
debug $LINENO "#### START PROXYSQL NODE MONITOR"
main
debug $LINENO "#### END PROXYSQL NODE MONITOR"

exit 0
