summaryrefslogtreecommitdiffstats
path: root/heartbeat/mysql
diff options
context:
space:
mode:
Diffstat (limited to 'heartbeat/mysql')
-rwxr-xr-xheartbeat/mysql1074
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: