123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086 |
- #!/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
|