diff options
Diffstat (limited to 'heartbeat/mysql')
-rwxr-xr-x | heartbeat/mysql | 1074 |
1 files changed, 1074 insertions, 0 deletions
diff --git a/heartbeat/mysql b/heartbeat/mysql new file mode 100755 index 0000000..1df2fc0 --- /dev/null +++ b/heartbeat/mysql @@ -0,0 +1,1074 @@ +#!/bin/sh +# +# +# MySQL +# +# Description: Manages a MySQL database as Linux-HA resource +# +# Authors: Alan Robertson: DB2 Script +# Jakub Janczak: rewrite as MySQL +# Andrew Beekhof: cleanup and import +# Sebastian Reitenbach: add OpenBSD defaults, more cleanup +# Narayan Newton: add Gentoo/Debian defaults +# Marian Marinov, Florian Haas: add replication capability +# Yves Trudeau, Baron Schwartz: add VIP support and improve replication +# +# Support: users@clusterlabs.org +# License: GNU General Public License (GPL) +# +# (c) 2002-2005 International Business Machines, Inc. +# 2005-2010 Linux-HA contributors +# +# An example usage in /etc/ha.d/haresources: +# node1 10.0.0.170 mysql +# +# See usage() function below for more details... +# +# OCF instance parameters: +# OCF_RESKEY_binary +# OCF_RESKEY_client_binary +# OCF_RESKEY_config +# OCF_RESKEY_datadir +# OCF_RESKEY_user +# OCF_RESKEY_group +# OCF_RESKEY_test_table +# OCF_RESKEY_test_user +# OCF_RESKEY_test_passwd +# OCF_RESKEY_enable_creation +# OCF_RESKEY_additional_parameters +# OCF_RESKEY_log +# OCF_RESKEY_pid +# OCF_RESKEY_socket +# OCF_RESKEY_replication_user +# OCF_RESKEY_replication_passwd +# OCF_RESKEY_replication_port +# OCF_RESKEY_max_slave_lag +# OCF_RESKEY_evict_outdated_slaves +# OCF_RESKEY_reader_attribute + +####################################################################### +# Initialization: + +: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} +. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs +. ${OCF_FUNCTIONS_DIR}/mysql-common.sh +####################################################################### + +usage() { + cat <<UEND +usage: $0 (start|stop|validate-all|meta-data|monitor|promote|demote|notify) + +$0 manages a MySQL Database as an HA resource. + +The 'start' operation starts the database. +The 'stop' operation stops the database. +The 'status' operation reports whether the database is running +The 'monitor' operation reports whether the database seems to be working +The 'promote' operation makes this mysql server run as master +The 'demote' operation makes this mysql server run as slave +The 'validate-all' operation reports whether the parameters are valid + +UEND +} + +meta_data() { + cat <<END +<?xml version="1.0"?> +<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> +<resource-agent name="mysql" version="1.0"> +<version>1.0</version> + +<longdesc lang="en"> +Resource script for MySQL. +May manage a standalone MySQL database, a clone set with externally +managed replication, or a complete master/slave replication setup. +Note, when master/slave replication is in use, the resource must +be setup to use notifications. Set 'notify=true' in the metadata +attributes when defining a MySQL master/slave instance. + +While managing replication, the default behavior is to use uname -n +values in the change master to command. Other IPs can be specified +manually by adding a node attribute \${INSTANCE_ATTR_NAME}_mysql_master_IP +giving the IP to use for replication. For example, if the mysql primitive +you are using is p_mysql, the attribute to set will be +p_mysql_mysql_master_IP. +</longdesc> +<shortdesc lang="en">Manages a MySQL database instance</shortdesc> +<parameters> + +<parameter name="binary" unique="0" required="0"> +<longdesc lang="en"> +Location of the MySQL server binary +</longdesc> +<shortdesc lang="en">MySQL server binary</shortdesc> +<content type="string" default="${OCF_RESKEY_binary_default}" /> +</parameter> + +<parameter name="client_binary" unique="0" required="0"> +<longdesc lang="en"> +Location of the MySQL client binary +</longdesc> +<shortdesc lang="en">MySQL client binary</shortdesc> +<content type="string" default="${OCF_RESKEY_client_binary_default}" /> +</parameter> + +<parameter name="config" unique="0" required="0"> +<longdesc lang="en"> +Configuration file +</longdesc> +<shortdesc lang="en">MySQL config</shortdesc> +<content type="string" default="${OCF_RESKEY_config_default}" /> +</parameter> + +<parameter name="datadir" unique="0" required="0"> +<longdesc lang="en"> +Directory containing databases +</longdesc> +<shortdesc lang="en">MySQL datadir</shortdesc> +<content type="string" default="${OCF_RESKEY_datadir_default}" /> +</parameter> + +<parameter name="user" unique="0" required="0"> +<longdesc lang="en"> +User running MySQL daemon +</longdesc> +<shortdesc lang="en">MySQL user</shortdesc> +<content type="string" default="${OCF_RESKEY_user_default}" /> +</parameter> + +<parameter name="group" unique="0" required="0"> +<longdesc lang="en"> +Group running MySQL daemon (for logfile and directory permissions) +</longdesc> +<shortdesc lang="en">MySQL group</shortdesc> +<content type="string" default="${OCF_RESKEY_group_default}"/> +</parameter> + +<parameter name="log" unique="0" required="0"> +<longdesc lang="en"> +The logfile to be used for mysqld. +</longdesc> +<shortdesc lang="en">MySQL log file</shortdesc> +<content type="string" default="${OCF_RESKEY_log_default}"/> +</parameter> + +<parameter name="pid" unique="0" required="0"> +<longdesc lang="en"> +The pidfile to be used for mysqld. +</longdesc> +<shortdesc lang="en">MySQL pid file</shortdesc> +<content type="string" default="${OCF_RESKEY_pid_default}"/> +</parameter> + +<parameter name="socket" unique="0" required="0"> +<longdesc lang="en"> +The socket to be used for mysqld. +</longdesc> +<shortdesc lang="en">MySQL socket</shortdesc> +<content type="string" default="${OCF_RESKEY_socket_default}"/> +</parameter> + +<parameter name="test_table" unique="0" required="0"> +<longdesc lang="en"> +Table to be tested in monitor statement (in database.table notation) +</longdesc> +<shortdesc lang="en">MySQL test table</shortdesc> +<content type="string" default="${OCF_RESKEY_test_table_default}" /> +</parameter> + +<parameter name="test_user" unique="0" required="0"> +<longdesc lang="en"> +MySQL test user, must have select privilege on test_table +</longdesc> +<shortdesc lang="en">MySQL test user</shortdesc> +<content type="string" default="${OCF_RESKEY_test_user_default}" /> +</parameter> + +<parameter name="test_passwd" unique="0" required="0"> +<longdesc lang="en"> +MySQL test user password +</longdesc> +<shortdesc lang="en">MySQL test user password</shortdesc> +<content type="string" default="${OCF_RESKEY_test_passwd_default}" /> +</parameter> + +<parameter name="enable_creation" unique="0" required="0"> +<longdesc lang="en"> +If the MySQL database does not exist, it will be created +</longdesc> +<shortdesc lang="en">Create the database if it does not exist</shortdesc> +<content type="boolean" default="${OCF_RESKEY_enable_creation_default}"/> +</parameter> + +<parameter name="additional_parameters" unique="0" required="0"> +<longdesc lang="en"> +Additional parameters which are passed to the mysqld on startup. +(e.g. --skip-external-locking or --skip-grant-tables) +</longdesc> +<shortdesc lang="en">Additional parameters to pass to mysqld</shortdesc> +<content type="string" default="${OCF_RESKEY_additional_parameters_default}"/> +</parameter> + +<parameter name="replication_user" unique="0" required="0"> +<longdesc lang="en"> +MySQL replication user. This user is used for starting and stopping +MySQL replication, for setting and resetting the master host, and for +setting and unsetting read-only mode. Because of that, this user must +have SUPER, REPLICATION SLAVE, REPLICATION CLIENT, PROCESS and RELOAD +privileges on all nodes within the cluster. Mandatory if you define a +master-slave resource. +</longdesc> +<shortdesc lang="en">MySQL replication user</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_user_default}" /> +</parameter> + +<parameter name="replication_passwd" unique="0" required="0"> +<longdesc lang="en"> +MySQL replication password. Used for replication client and slave. +Mandatory if you define a master-slave resource. +</longdesc> +<shortdesc lang="en">MySQL replication user password</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_passwd_default}" /> +</parameter> + +<parameter name="replication_port" unique="0" required="0"> +<longdesc lang="en"> +The port on which the Master MySQL instance is listening. +</longdesc> +<shortdesc lang="en">MySQL replication port</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_port_default}" /> +</parameter> + +<parameter name="replication_require_ssl" unique="0" required="0"> +<longdesc lang="en"> +Enables SSL connection to local MySQL service for replication user. +i.e. if REQUIRE SSL for replication user in MySQL set, this should be set to "true". +</longdesc> +<shortdesc lang="en">MySQL replication require ssl</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_require_ssl_default}" /> +</parameter> + +<parameter name="replication_master_ssl_ca" unique="0" required="0"> +<longdesc lang="en"> +The SSL CA certificate to be used for replication over SSL. +</longdesc> +<shortdesc lang="en">MySQL replication SSL CA certificate</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_master_ssl_ca_default}" /> +</parameter> + +<parameter name="replication_master_ssl_cert" unique="0" required="0"> +<longdesc lang="en"> +The SSL CA certificate to be used for replication over SSL. +</longdesc> +<shortdesc lang="en">MySQL replication SSL certificate</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_master_ssl_cert_default}" /> +</parameter> + +<parameter name="replication_master_ssl_key" unique="0" required="0"> +<longdesc lang="en"> +The SSL certificate key to be used for replication over SSL. +</longdesc> +<shortdesc lang="en">MySQL replication SSL certificate key</shortdesc> +<content type="string" default="${OCF_RESKEY_replication_master_ssl_key_default}" /> +</parameter> + +<parameter name="max_slave_lag" unique="0" required="0"> +<longdesc lang="en"> +The maximum number of seconds a replication slave is allowed to lag +behind its master. Do not set this to zero. What the cluster manager +does in case a slave exceeds this maximum lag is determined by the +evict_outdated_slaves parameter. +</longdesc> +<shortdesc lang="en">Maximum time (seconds) a MySQL slave is allowed +to lag behind a master</shortdesc> +<content type="integer" default="${OCF_RESKEY_max_slave_lag_default}"/> +</parameter> + +<parameter name="evict_outdated_slaves" unique="0" required="0"> +<longdesc lang="en"> +If set to true, any slave which is more than max_slave_lag seconds +behind the master has its MySQL instance shut down. If this parameter +is set to false in a primitive or clone resource, it is simply +ignored. If set to false in a master/slave resource, then exceeding +the maximum slave lag will merely push down the master preference so +the lagging slave is never promoted to the new master. +</longdesc> +<shortdesc lang="en">Determines whether to shut down badly lagging +slaves</shortdesc> +<content type="boolean" default="${OCF_RESKEY_evict_outdated_slaves_default}" /> +</parameter> + +<parameter name="reader_attribute" unique="1" required="0"> +<longdesc lang="en"> +An attribute that the RA can manage to specify whether a node +can be read from. This node attribute will be 1 if it's fine to +read from the node, and 0 otherwise (for example, when a slave +has lagged too far behind the master). + +A typical example for the use of this attribute would be to tie +a set of IP addresses to MySQL slaves that can be read from. + +This parameter is only meaningful in master/slave set configurations. +</longdesc> +<shortdesc lang="en">Sets the node attribute that determines +whether a node is usable for clients to read from.</shortdesc> +<content type="string" default="${OCF_RESKEY_reader_attribute_default}" /> +</parameter> +</parameters> + +<actions> +<action name="start" timeout="120s" /> +<action name="stop" timeout="120s" /> +<action name="status" timeout="60s" /> +<action name="monitor" depth="0" timeout="30s" interval="20s" /> +<action name="monitor" role="Promoted" depth="0" timeout="30s" interval="10s" /> +<action name="monitor" role="Unpromoted" depth="0" timeout="30s" interval="30s" /> +<action name="promote" timeout="120s" /> +<action name="demote" timeout="120s" /> +<action name="notify" timeout="90s" /> +<action name="validate-all" timeout="5s" /> +<action name="meta-data" timeout="5s" /> +</actions> +</resource-agent> +END +} + +# Convenience functions + +set_read_only() { + # Sets or unsets read-only mode. Accepts one boolean as its + # optional argument. If invoked without any arguments, defaults to + # enabling read only mode. Should only be set in master/slave + # setups. + # Returns $OCF_SUCCESS if the operation succeeds, or + # $OCF_ERR_GENERIC if it fails. + local ro_val + if ocf_is_true $1; then + ro_val="on" + else + ro_val="off" + fi + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "SET GLOBAL read_only=${ro_val}" +} + +get_read_only() { + # Check if read-only is set + local read_only_state + + read_only_state=`$MYSQL $MYSQL_OPTIONS_REPL \ + --skip-column-names -e "SHOW VARIABLES LIKE 'read_only'" | awk '{print $2}'` + + if [ "$read_only_state" = "ON" ]; then + return 0 + else + return 1 + fi +} + +is_slave() { + # Determine whether the machine is currently running as a MySQL + # slave, as determined per SHOW SLAVE STATUS. Returns 1 if SHOW + # SLAVE STATUS creates an empty result set, 0 otherwise. + local rc + local tmpfile + + # Check whether this machine should be slave + if ! ocf_is_ms || ! get_read_only; then + return 1 + fi + + get_slave_info + rc=$? + rm -f $tmpfile + + if [ $rc -eq 0 ]; then + # show slave status is not empty + # Is there a master_log_file defined? (master_log_file is deleted + # by reset slave + if [ "$master_log_file" ]; then + return 0 + else + return 1 + fi + else + # "SHOW SLAVE STATUS" returns an empty set if instance is not a + # replication slave + return 1 + fi + +} + +parse_slave_info() { + # Extracts field $1 from result of "SHOW SLAVE STATUS\G" from file $2 + sed -ne "s/^.* $1: \(.*\)$/\1/p" < $2 +} + +get_slave_info() { + # Warning: this sets $tmpfile and LEAVE this file! You must delete it after use! + local mysql_options + + if [ "$master_log_file" -a "$master_host" ]; then + # variables are already defined, get_slave_info has been run before + return $OCF_SUCCESS + else + tmpfile=`mktemp ${HA_RSCTMP}/check_slave.${OCF_RESOURCE_INSTANCE}.XXXXXX` + + $MYSQL $MYSQL_OPTIONS_REPL \ + -e 'SHOW SLAVE STATUS\G' > $tmpfile + + if [ -s $tmpfile ]; then + master_host=`parse_slave_info Master_Host $tmpfile` + master_user=`parse_slave_info Master_User $tmpfile` + master_port=`parse_slave_info Master_Port $tmpfile` + master_log_file=`parse_slave_info Master_Log_File $tmpfile` + master_log_pos=`parse_slave_info Read_Master_Log_Pos $tmpfile` + slave_sql=`parse_slave_info Slave_SQL_Running $tmpfile` + slave_io=`parse_slave_info Slave_IO_Running $tmpfile` + last_errno=`parse_slave_info Last_Errno $tmpfile` + secs_behind=`parse_slave_info Seconds_Behind_Master $tmpfile` + ocf_log debug "MySQL instance running as a replication slave" + else + # Instance produced an empty "SHOW SLAVE STATUS" output -- + # instance is not a slave + ocf_exit_reason "check_slave invoked on an instance that is not a replication slave." + return $OCF_ERR_GENERIC + fi + + return $OCF_SUCCESS + fi +} + +check_slave() { + # Checks slave status + local rc new_master + + get_slave_info + rc=$? + + if [ $rc -eq 0 ]; then + # Did we receive an error other than max_connections? + if [ $last_errno -ne 0 -a $last_errno -ne "$MYSQL_TOO_MANY_CONN_ERR" ]; then + # Whoa. Replication ran into an error. This slave has + # diverged from its master. Make sure this resource + # doesn't restart in place. + ocf_exit_reason "MySQL instance configured for replication, but replication has failed." + ocf_log err "See $tmpfile for details" + + # Just pull the reader VIP away, killing MySQL here would be pretty evil + # on a loaded server + + set_reader_attr 0 + exit $OCF_SUCCESS + + fi + + # If we got max_connections, let's remove the vip + if [ $last_errno -eq "$MYSQL_TOO_MANY_CONN_ERR" ]; then + set_reader_attr 0 + exit $OCF_SUCCESS + fi + + if [ "$slave_io" != 'Yes' ]; then + # Not necessarily a bad thing. The master may have + # temporarily shut down, and the slave may just be + # reconnecting. A warning can't hurt, though. + ocf_log warn "MySQL Slave IO threads currently not running." + + # Sanity check, are we at least on the right master + new_master=`$CRM_ATTR_REPL_INFO --query -q | cut -d'|' -f1` + + if [ "$master_host" != "$new_master" ]; then + # Not pointing to the right master, not good, removing the VIPs + set_reader_attr 0 + + exit $OCF_SUCCESS + fi + + fi + + if [ "$slave_sql" != 'Yes' ]; then + # We don't have a replication SQL thread running. Not a + # good thing. Try to recoved by restarting the SQL thread + # and remove reader vip. Prevent MySQL restart. + ocf_exit_reason "MySQL Slave SQL threads currently not running." + ocf_log err "See $tmpfile for details" + + # Remove reader vip + set_reader_attr 0 + + # try to restart slave + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "START SLAVE" + + # Return success to prevent a restart + exit $OCF_SUCCESS + fi + + if ocf_is_true $OCF_RESKEY_evict_outdated_slaves; then + # We're supposed to bail out if we lag too far + # behind. Let's check our lag. + if [ "$secs_behind" = "NULL" ] || [ $secs_behind -gt $OCF_RESKEY_max_slave_lag ]; then + ocf_exit_reason "MySQL Slave is $secs_behind seconds behind master (allowed maximum: $OCF_RESKEY_max_slave_lag)." + ocf_log err "See $tmpfile for details" + + # Remove reader vip + set_reader_attr 0 + + exit $OCF_ERR_INSTALLED + fi + fi + + # is the slave ok to have a VIP on it + if [ "$secs_behind" = "NULL" ] || [ $secs_behind -gt $OCF_RESKEY_max_slave_lag ]; then + set_reader_attr 0 + else + set_reader_attr 1 + fi + + ocf_log debug "MySQL instance running as a replication slave" + rm -f $tmpfile + else + # Instance produced an empty "SHOW SLAVE STATUS" output -- + # instance is not a slave + # TODO: Needs to handle when get_slave_info will return too many connections error + rm -f $tmpfile + ocf_exit_reason "check_slave invoked on an instance that is not a replication slave." + exit $OCF_ERR_GENERIC + fi +} + +set_master() { + local new_master master_log_file master_log_pos + local master_params master_ssl_params + + new_master=`$CRM_ATTR_REPL_INFO --query -q | cut -d'|' -f1` + + # Keep replication position + get_slave_info + + if [ "$master_log_file" -a "$new_master" = "$master_host" ]; then + # master_params=", MASTER_LOG_FILE='$master_log_file', \ + # MASTER_LOG_POS=$master_log_pos" + ocf_log info "Kept master pos for $master_host : $master_log_file:$master_log_pos" + rm -f $tmpfile + return + else + master_log_file=`$CRM_ATTR_REPL_INFO --query -q | cut -d'|' -f2` + master_log_pos=`$CRM_ATTR_REPL_INFO --query -q | cut -d'|' -f3` + if [ -n "$master_log_file" -a -n "$master_log_pos" ]; then + master_params=", MASTER_LOG_FILE='$master_log_file', \ + MASTER_LOG_POS=$master_log_pos" + ocf_log info "Restored master pos for $new_master : $master_log_file:$master_log_pos" + fi + fi + + # Informs the MySQL server of the master to replicate + # from. Accepts one mandatory argument which must contain the host + # name of the new master host. The master must either be unchanged + # from the last master the slave replicated from, or freshly + # reset with RESET MASTER. + if [ -n "$OCF_RESKEY_replication_master_ssl_ca" ] && [ -n "$OCF_RESKEY_replication_master_ssl_cert" ] && [ -n "$OCF_RESKEY_replication_master_ssl_key" ]; then + master_ssl_params=", MASTER_SSL=1, \ + MASTER_SSL_CA='$OCF_RESKEY_replication_master_ssl_ca', \ + MASTER_SSL_CERT='$OCF_RESKEY_replication_master_ssl_cert', \ + MASTER_SSL_KEY='$OCF_RESKEY_replication_master_ssl_key'" + fi + + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "CHANGE MASTER TO MASTER_HOST='$new_master', \ + MASTER_PORT=$OCF_RESKEY_replication_port, \ + MASTER_USER='$OCF_RESKEY_replication_user', \ + MASTER_PASSWORD='$OCF_RESKEY_replication_passwd' $master_params $master_ssl_params" + rm -f $tmpfile +} + +unset_master(){ + # Instructs the MySQL server to stop replicating from a master + # host. + + # If we're currently not configured to be replicating from any + # host, then there's nothing to do. But we do log a warning as + # no-one but the CRM should be touching the MySQL master/slave + # configuration. + if ! is_slave; then + ocf_log warn "Attempted to unset the replication master on an instance that is not configured as a replication slave" + return $OCF_SUCCESS + fi + + local tmpfile + tmpfile=`mktemp ${HA_RSCTMP}/unset_master.${OCF_RESOURCE_INSTANCE}.XXXXXX` + + # At this point, the master is read only so there should not be much binlogs to transfer + # Let's wait for the last bits + while true; do + $MYSQL $MYSQL_OPTIONS_REPL \ + -e 'SHOW PROCESSLIST\G' > $tmpfile + if grep -i 'Waiting for master to send event' $tmpfile >/dev/null; then + ocf_log info "MySQL slave has finished reading master binary log" + break + fi + if grep -i 'Reconnecting after a failed master event read' $tmpfile >/dev/null; then + ocf_log info "Master is down, no more binary logs to come" + break + fi + if grep -i 'Connecting to master' $tmpfile >/dev/null; then + ocf_log info "Master is down, no more binary logs to come" + break + fi + if ! grep 'system user' $tmpfile >/dev/null; then + ocf_log info "Slave is not running - not waiting to finish" + break + fi + + sleep 1 + done + + # Now, stop the slave I/O thread and wait for relay log + # processing to complete + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "STOP SLAVE IO_THREAD" + if [ $? -gt 0 ]; then + ocf_exit_reason "Error stopping slave IO thread" + exit $OCF_ERR_GENERIC + fi + + while true; do + $MYSQL $MYSQL_OPTIONS_REPL \ + -e 'SHOW PROCESSLIST\G' > $tmpfile + if grep -i 'Has read all relay log' $tmpfile >/dev/null; then + ocf_log info "MySQL slave has finished processing relay log" + break + fi + if ! grep -q 'system user' $tmpfile; then + ocf_log info "Slave not runnig - not waiting to finish" + break + fi + ocf_log info "Waiting for MySQL slave to finish processing relay log" + sleep 1 + done + rm -f $tmpfile + + # Now, stop all slave activity and unset the master host + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "STOP SLAVE" + if [ $? -gt 0 ]; then + ocf_exit_reason "Error stopping rest slave threads" + exit $OCF_ERR_GENERIC + fi + + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "RESET SLAVE /*!50516 ALL */;" + if [ $? -gt 0 ]; then + ocf_exit_reason "Failed to reset slave" + exit $OCF_ERR_GENERIC + fi +} + +# Start replication as slave +start_slave() { + + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "START SLAVE" +} + +# Set the attribute controlling the readers VIP +set_reader_attr() { + local curr_attr_value + + curr_attr_value=$(get_reader_attr) + + if [ "$curr_attr_value" -ne "$1" ]; then + $CRM_ATTR -l reboot --name ${OCF_RESKEY_reader_attribute} -v $1 + fi + +} + +# get the attribute controlling the readers VIP +get_reader_attr() { + local attr_value + local rc + + attr_value=`$CRM_ATTR -l reboot --name ${OCF_RESKEY_reader_attribute} --query -q` + rc=$? + if [ "$rc" -eq "0" ]; then + echo $attr_value + else + echo -1 + fi + +} + +# Stores data for MASTER STATUS from MySQL +update_data_master_status() { + + master_status_file="${HA_RSCTMP}/master_status.${OCF_RESOURCE_INSTANCE}" + + $MYSQL $MYSQL_OPTIONS_REPL -e "SHOW MASTER STATUS\G" > $master_status_file +} + + +# Returns the specified value from the stored copy of SHOW MASTER STATUS. +# should be call after update_data_master_status for tmpfile +# Arguments: +# $1 The value to get. +get_master_status() { + awk -v var="$1" '$1 == var ":" {print substr($0, index($0, ":") + 2)}' "$master_status_file" +} + + +# Determines what IP address is attached to the current host. The output of the +# crm_attribute command looks like this: +# scope=nodes name=IP value=10.2.2.161 +# If the ${INSTANCE_ATTR_NAME}_MYSQL_MASTER_IP node attribute is not defined, fallback is to uname -n +# The ${INSTANCE_ATTR_NAME}_MYSQL_MASTER_IP is the IP address that will be used for the +# change master to command. +get_local_ip() { + local IP + IP=`$CRM_ATTR -l forever -n ${INSTANCE_ATTR_NAME}_mysql_master_IP -q -G` + if [ ! $? -eq 0 ]; then + uname -n + else + echo $IP + fi +} + +####################################################################### + +# Functions invoked by resource manager actions + +mysql_monitor() { + local rc + local status_loglevel="err" + + # Set loglevel to info during probe + if ocf_is_probe; then + status_loglevel="info" + fi + + if ocf_is_ms; then + OCF_CHECK_LEVEL=10 + fi + + mysql_common_status $status_loglevel + rc=$? + + # TODO: check max connections error + + # If status returned an error, return that immediately + if [ $rc -ne $OCF_SUCCESS ]; then + if ocf_is_ms ; then + # This is a master slave setup but monitored host returned some errors. + # Immediately remove it from the pool of possible masters by erasing its master-mysql key + # When new mysql master election is started and node got no or negative master-mysql attribute the following is logged + # nodename.com pengine: debug: master_color: mysql:0 master score: -1 + # If there are NO nodes with positive vaule election of mysql master will fail with + # nodename.com pengine: info: master_color: ms_mysql: Promoted 0 instances of a possible 1 to master + ocf_promotion_score -D + fi + + return $rc + fi + + if [ $OCF_CHECK_LEVEL -eq 10 ]; then + if [ -z "$OCF_RESKEY_test_table" ]; then + ocf_exit_reason "test_table not set" + return $OCF_ERR_CONFIGURED + + fi + + # Check if this instance is configured as a slave, and if so + # check slave status + if is_slave; then + check_slave + fi + + # Check for test table + ocf_run -q $MYSQL $MYSQL_OPTIONS_TEST \ + -e "SELECT COUNT(*) FROM $OCF_RESKEY_test_table" + rc=$? + + if [ $rc -ne 0 ]; then + # We are master/slave and test failed. Delete master score for this node as it is considered unhealthy because of this particular failed check. + ocf_is_ms && ocf_promotion_score -D + ocf_exit_reason "Failed to select from $test_table"; + return $OCF_ERR_GENERIC; + fi + fi + + if ocf_is_ms && ! get_read_only; then + ocf_log debug "MySQL monitor succeeded (master)"; + # Always set master score for the master + ocf_promotion_score -v $((${OCF_RESKEY_max_slave_lag}+1)) + return $OCF_RUNNING_MASTER + else + ocf_log debug "MySQL monitor succeeded"; + ocf_is_ms && ocf_promotion_score -v 1 + return $OCF_SUCCESS + fi +} + +mysql_start() { + local rc + + if ocf_is_ms; then + # Initialize the ReaderVIP attribute, monitor will enable it + set_reader_attr 0 + fi + + mysql_common_status info + if [ $? = $OCF_SUCCESS ]; then + ocf_log info "MySQL already running" + return $OCF_SUCCESS + fi + + mysql_common_prepare_dirs + + # Uncomment to perform permission clensing + # - not convinced this should be enabled by default + # + #chmod 0755 $OCF_RESKEY_datadir + #chown -R $OCF_RESKEY_user $OCF_RESKEY_datadir + #chgrp -R $OCF_RESKEY_group $OCF_RESKEY_datadir + mysql_extra_params= + if ocf_is_ms; then + mysql_extra_params="--skip-slave-start" + fi + + mysql_common_start $mysql_extra_params + rc=$? + if [ $rc != $OCF_SUCCESS ]; then + return $rc + fi + + if ocf_is_ms; then + # We're configured as a stateful resource. We must start as + # slave by default. At this point we don't know if the CRM has + # already promoted a master. So, we simply start in read only + # mode. + set_read_only on + + # Now, let's see whether there is a master. We might be a new + # node that is just joining the cluster, and the CRM may have + # promoted a master before. + master_host=`echo $OCF_RESKEY_CRM_meta_notify_master_uname|tr -d " "` + if [ "$master_host" -a "$master_host" != ${NODENAME} ]; then + ocf_log info "Changing MySQL configuration to replicate from $master_host." + set_master + start_slave + if [ $? -ne 0 ]; then + ocf_exit_reason "Failed to start slave" + return $OCF_ERR_GENERIC + fi + else + ocf_log info "No MySQL master present - clearing replication state" + unset_master + fi + + # We also need to set a master preference, otherwise Pacemaker + # won't ever promote us in the absence of any explicit + # preference set by the administrator. We choose a low + # greater-than-zero preference. + ocf_promotion_score -v 1 + fi + + # Initial monitor action + if [ -n "$OCF_RESKEY_test_table" -a -n "$OCF_RESKEY_test_user" -a -n "$OCF_RESKEY_test_passwd" ]; then + OCF_CHECK_LEVEL=10 + fi + mysql_monitor + rc=$? + if [ $rc != $OCF_SUCCESS -a $rc != $OCF_RUNNING_MASTER ]; then + ocf_exit_reason "Failed initial monitor action" + return $rc + fi + + ocf_log info "MySQL started" + return $OCF_SUCCESS +} + +mysql_stop() { + if ocf_is_ms; then + # clear preference for becoming master + ocf_promotion_score -D + + # Remove VIP capability + set_reader_attr 0 + fi + + mysql_common_stop +} + +mysql_promote() { + local master_info + + if ( ! mysql_common_status err ); then + return $OCF_NOT_RUNNING + fi + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "STOP SLAVE" + + # Set Master Info in CIB, cluster level attribute + update_data_master_status + master_info="$(get_local_ip)|$(get_master_status File)|$(get_master_status Position)" + ${CRM_ATTR_REPL_INFO} -v "$master_info" + rm -f $tmpfile + + set_read_only off || return $OCF_ERR_GENERIC + + # Existing master gets a higher-than-default master preference, so + # the cluster manager does not shuffle the master role around + # unnecessarily + ocf_promotion_score -v $((${OCF_RESKEY_max_slave_lag}+1)) + + # A master can accept reads + set_reader_attr 1 + + return $OCF_SUCCESS +} + +mysql_demote() { + if ! mysql_common_status err; then + return $OCF_NOT_RUNNING + fi + + # Return master preference to default, so the cluster manager gets + # a chance to select a new master + ocf_promotion_score -v 1 +} + +mysql_notify() { + # If not configured as a Stateful resource, we make no sense of + # notifications. + if ! ocf_is_ms; then + ocf_log info "This agent makes no use of notifications unless running in master/slave mode." + return $OCF_SUCCESS + fi + + local type_op + type_op="${OCF_RESKEY_CRM_meta_notify_type}-${OCF_RESKEY_CRM_meta_notify_operation}" + + ocf_log debug "Received $type_op notification." + + case "$type_op" in + 'pre-promote') + # Nothing to do now here, new replication info not yet published + + ;; + 'post-promote') + # The master has completed its promotion. Now is a good + # time to check whether our replication slave is working + # correctly. + master_host=`echo $OCF_RESKEY_CRM_meta_notify_promote_uname|tr -d " "` + if [ "$master_host" = ${NODENAME} ]; then + ocf_log info "This will be the new master, ignoring post-promote notification." + else + ocf_log info "Resetting replication" + unset_master + if [ $? -ne 0 ]; then + return $OCF_ERR_GENERIC + fi + + ocf_log info "Changing MySQL configuration to replicate from $master_host" + set_master + if [ $? -ne 0 ]; then + return $OCF_ERR_GENERIC + fi + + start_slave + if [ $? -ne 0 ]; then + ocf_exit_reason "Failed to start slave" + return $OCF_ERR_GENERIC + fi + fi + return $OCF_SUCCESS + ;; + 'pre-demote') + demote_host=`echo $OCF_RESKEY_CRM_meta_notify_demote_uname|tr -d " "` + if [ $demote_host = ${NODENAME} ]; then + ocf_log info "post-demote notification for $demote_host" + set_read_only on + if [ $? -ne 0 ]; then + ocf_exit_reason "Failed to set read-only"; + return $OCF_ERR_GENERIC; + fi + + # Must kill all existing user threads because they are still Read/write + # in order for the slaves to complete the read of binlogs + local tmpfile + tmpfile=`mktemp ${HA_RSCTMP}/threads.${OCF_RESOURCE_INSTANCE}.XXXXXX` + $MYSQL $MYSQL_OPTIONS_REPL \ + -e "SHOW PROCESSLIST" > $tmpfile + + for thread in `awk '$0 !~ /Binlog Dump|system user|event_scheduler|SHOW PROCESSLIST/ && $0 ~ /^[0-9]/ {print $1}' $tmpfile` + do + ocf_run $MYSQL $MYSQL_OPTIONS_REPL \ + -e "KILL ${thread}" + done + else + ocf_log info "Ignoring post-demote notification execpt for my own demotion." + fi + return $OCF_SUCCESS + ;; + 'post-demote') + demote_host=`echo $OCF_RESKEY_CRM_meta_notify_demote_uname|tr -d " "` + if [ $demote_host = ${NODENAME} ]; then + ocf_log info "Ignoring post-demote notification for my own demotion." + return $OCF_SUCCESS + fi + ocf_log info "post-demote notification for $demote_host." + # The former master has just been gracefully demoted. + unset_master + ;; + *) + return $OCF_SUCCESS + ;; + esac +} + +####################################################################### + +case "$1" in + meta-data) meta_data + exit $OCF_SUCCESS;; + usage|help) usage + exit $OCF_SUCCESS;; +esac + +mysql_common_validate +rc=$? +LSB_STATUS_STOPPED=3 +if [ $rc -ne 0 ]; then + case "$1" in + stop) ;; + monitor) + mysql_common_status "info" + if [ $? -eq $OCF_SUCCESS ]; then + # if validatation fails and pid is active, always treat this as an error + ocf_exit_reason "environment validation failed, active pid is in unknown state." + exit $OCF_ERR_GENERIC + fi + # validation failed and pid is not active, it's safe to say this instance is inactive. + exit $OCF_NOT_RUNNING;; + + status) exit $LSB_STATUS_STOPPED;; + *) exit $rc;; + esac +fi + +# What kind of method was invoked? +case "$1" in + start) mysql_start;; + stop) mysql_stop;; + status) mysql_common_status err;; + monitor) mysql_monitor;; + promote) mysql_promote;; + demote) mysql_demote;; + notify) mysql_notify;; + validate-all) exit $OCF_SUCCESS;; + + *) usage + exit $OCF_ERR_UNIMPLEMENTED;; +esac + +# vi:sw=4:ts=4:et: |