tools/ctdb: "ctdb runstate" now accepts optional expected run state arguments
[obnox/ctdb.git] / config / ctdb.init
index 9b965a900d9eea84ed8a85c0d70d485af5440305..70dcfa642169408d32bf773942764e59ecb8c110 100755 (executable)
@@ -6,7 +6,7 @@
 # chkconfig:           - 90 01
 #
 # description:                 Starts and stops the clustered tdb daemon
-# pidfile:             /var/run/ctdbd/ctdbd.pid
+# pidfile:             /var/run/ctdb/ctdbd.pid
 #
 
 ### BEGIN INIT INFO
@@ -32,6 +32,10 @@ fi
     LC_ALL=en_US.UTF-8
 }
 
+if [ -f /lib/lsb/init-functions ] ; then
+    . /lib/lsb/init-functions
+fi
+
 # Avoid using root's TMPDIR
 unset TMPDIR
 
@@ -43,10 +47,6 @@ unset TMPDIR
 loadconfig network
 loadconfig ctdb
 
-[ -z "$CTDB_RECOVERY_LOCK" ] && {
-    echo "No recovery lock specified. Starting CTDB without split brain prevention"
-}
-
 # check networking is up (for redhat)
 [ "$NETWORKING" = "no" ] && exit 0
 
@@ -54,6 +54,7 @@ detect_init_style
 export CTDB_INIT_STYLE
 
 ctdbd=${CTDBD:-/usr/sbin/ctdbd}
+pidfile="/var/run/ctdb/ctdbd.pid"
 
 if [ "$CTDB_VALGRIND" = "yes" ]; then
     init_style="valgrind"
@@ -69,7 +70,7 @@ build_ctdb_options () {
        # then return
        [ -z "$2" -o \( -n "$3" -a "$3" != "$2" \) ] && return
 
-       val="$2"
+       val="'$2'"
        case "$1" in
            --*) sep="=" ;;
            -*)  sep=" " ;;
@@ -83,8 +84,14 @@ build_ctdb_options () {
        CTDB_OPTIONS="${CTDB_OPTIONS}${CTDB_OPTIONS:+ }${1}${sep}${val}"
     }
 
+    [ -z "$CTDB_RECOVERY_LOCK" ] && {
+        echo "No recovery lock specified. Starting CTDB without split brain prevention"
+    }
     maybe_set "--reclock"                "$CTDB_RECOVERY_LOCK"
 
+    mkdir -p $(dirname "$pidfile")
+    maybe_set "--pidfile"                "$pidfile"
+
     # build up CTDB_OPTIONS variable from optional parameters
     maybe_set "--logfile"                "$CTDB_LOGFILE"
     maybe_set "--nlist"                  "$CTDB_NODES"
@@ -103,79 +110,202 @@ build_ctdb_options () {
     maybe_set "--no-lmaster"             "$CTDB_CAPABILITY_LMASTER"   "no"
     maybe_set "--lvs --single-public-ip" "$CTDB_LVS_PUBLIC_IP"
     maybe_set "--script-log-level"       "$CTDB_SCRIPT_LOG_LEVEL"
+    maybe_set "--log-ringbuf-size"       "$CTDB_LOG_RINGBUF_SIZE"
     maybe_set "--syslog"                 "$CTDB_SYSLOG"               "yes"
+    maybe_set "--max-persistent-check-errors" "$CTDB_MAX_PERSISTENT_CHECK_ERRORS"
+}
+
+export_debug_variables ()
+{
+    export CTDB_DEBUG_HUNG_SCRIPT CTDB_EXTERNAL_TRACE
+}
+
+# Log given message or stdin to either syslog or a CTDB log file
+do_log ()
+{
+    script_log "ctdb.init" "$@"
 }
 
-check_persistent_databases () {
-    PERSISTENT_DB_DIR="${CTDB_DBDIR:-/var/ctdb}/persistent"
-    mkdir -p $PERSISTENT_DB_DIR 2>/dev/null
-    for PDBASE in `ls $PERSISTENT_DB_DIR/*.tdb.[0-9] 2>/dev/null`; do
-       /usr/bin/tdbdump $PDBASE >/dev/null 2>/dev/null || {
-           echo "Persistent database $PDBASE is corrupted! CTDB will not start."
+select_tdb_checker ()
+{
+    # Find the best TDB consistency check available.
+    use_tdb_tool_check=false
+    if which tdbtool >/dev/null 2>&1 && \
+       echo "help" | tdbtool | grep -q check ; then
+
+       use_tdb_tool_check=true
+    elif which tdbtool >/dev/null 2>&1 && which tdbdump >/dev/null 2>&1 ; then
+           do_log <<EOF
+WARNING: The installed 'tdbtool' does not offer the 'check' subcommand.
+ Using 'tdbdump' for database checks.
+ Consider updating 'tdbtool' for better checks!
+EOF
+    elif which tdbdump >/dev/null 2>&1 ; then
+       do_log <<EOF
+WARNING: 'tdbtool' is not available.
+ Using 'tdbdump' to check the databases.
+ Consider installing a recent 'tdbtool' for better checks!
+EOF
+    else
+       do_log <<EOF
+WARNING: Cannot check databases since neither
+ 'tdbdump' nor 'tdbtool check' is available.
+ Consider installing tdbtool or at least tdbdump!
+EOF
+        return 1
+    fi
+}
+
+check_tdb ()
+{
+    _db="$1"
+
+    if $use_tdb_tool_check ; then
+       # tdbtool always exits with 0  :-(
+       if tdbtool "$_db" check 2>/dev/null |
+           grep -q "Database integrity is OK" ; then
+           return 0
+       else
+           return 1
+       fi
+    else
+       tdbdump "$_db" >/dev/null 2>/dev/null
+       return $?
+    fi
+}
+
+check_persistent_databases ()
+{
+    _dir="${CTDB_DBDIR_PERSISTENT:-${CTDB_DBDIR:-/var/ctdb}/persistent}"
+    mkdir -p "$_dir" 2>/dev/null
+
+    [ "${CTDB_MAX_PERSISTENT_CHECK_ERRORS:-0}" = "0" ] || return 0
+
+    for _db in $(ls "$_dir/"*.tdb.*[0-9] 2>/dev/null) ; do
+       check_tdb $_db || {
+           do_log "Persistent database $_db is corrupted! CTDB will not start."
            return 1
        }
     done
 }
 
-set_ctdb_variables () {
-    # set any tunables from the config file
-    set | grep ^CTDB_SET_ | cut -d_ -f3- | 
-    while read v; do
-       varname=`echo $v | cut -d= -f1`
-       value=`echo $v | cut -d= -f2`
-       ctdb setvar $varname $value || RETVAL=1
-    done || exit 1
+check_non_persistent_databases ()
+{
+    _dir="${CTDB_DBDIR:-/var/ctdb}"
+    mkdir -p "$_dir" 2>/dev/null
+
+    for _db in $(ls "${_dir}/"*.tdb.*[0-9] 2>/dev/null) ; do
+       check_tdb $_db || {
+           _backup="${_db}.$(date +'%Y%m%d.%H%M%S.%N').corrupt"
+           do_log <<EOF
+WARNING: database ${_db} is corrupted.
+ Moving to backup ${_backup} for later analysis.
+EOF
+           mv "$_db" "$_backup"
+
+           # Now remove excess backups
+           ls -td "${_db}."*".corrupt" |
+           tail -n +$((${CTDB_MAX_CORRUPT_DB_BACKUPS:-10} + 1)) |
+           xargs rm -f
+           
+       }
+    done
 }
 
 set_retval() {
     return $1
 }
 
-ctdbd=${CTDBD:-/usr/sbin/ctdbd}
+wait_until_ready () {
+    _timeout="${1:-10}" # default is 10 seconds
+
+    _count=0
+    while ! ctdb runstate startup running >/dev/null 2>&1 ; do
+       if [ $_count -ge $_timeout ] ; then
+           return 1
+       fi
+       sleep 1
+       _count=$(($_count + 1))
+    done
+}
 
 start() {
     echo -n $"Starting ctdbd service: "
 
     ctdb ping >/dev/null 2>&1 && {
        echo $"CTDB is already running"
-       return 1
+       return 0
     }
 
+    # About to start new $ctdbd.  The ping above has failed and any
+    # new $ctdbd will destroy the Unix domain socket, so any processes
+    # that aren't yet completely useless soon will be...  so kill
+    # them.
+    pkill -9 -f "$ctdbd"
+
     build_ctdb_options
 
-    check_persistent_databases || return $?
+    export_debug_variables
+
+    # make sure we drop any ips that might still be held if previous
+    # instance of ctdb got killed with -9 or similar
+    drop_all_public_ips "ctdb.init"
+
+    if select_tdb_checker ; then
+       check_persistent_databases || return $?
+       check_non_persistent_databases
+    fi
+
+    if [ "$CTDB_SUPPRESS_COREFILE" = "yes" ]; then
+       ulimit -c 0
+    else
+       ulimit -c unlimited
+    fi
 
     case $init_style in
        valgrind)
-           valgrind -q --log-file=/var/log/ctdb_valgrind \
-               $ctdbd --nosetsched $CTDB_OPTIONS 
+           eval valgrind -q --log-file=/var/log/ctdb_valgrind \
+               $ctdbd --valgrinding "$CTDB_OPTIONS"
            RETVAL=$?
            echo
            ;;
        suse)
-           startproc $ctdbd $CTDB_OPTIONS
-           rc_status -v
+           eval startproc $ctdbd "$CTDB_OPTIONS"
            RETVAL=$?
            ;;
        redhat)
-           daemon $ctdbd $CTDB_OPTIONS
+           eval $ctdbd "$CTDB_OPTIONS"
            RETVAL=$?
-           echo
            [ $RETVAL -eq 0 ] && touch /var/lock/subsys/ctdb || RETVAL=1
            ;;
        debian)
-           start-stop-daemon --start --quiet --background \
-               --exec $ctdbd -- $CTDB_OPTIONS
+           eval start-stop-daemon --start --quiet --background \
+               --exec $ctdbd -- "$CTDB_OPTIONS"
            RETVAL=$?
            ;;
     esac
 
-    sleep 1
+    if [ $RETVAL -eq 0 ] ; then
+       if ! wait_until_ready ; then
+           RETVAL=1
+           echo "Timed out waiting for initialisation - killing CTDB"
+           pkill -9 -f $ctdbd >/dev/null 2>&1
+       fi
+    fi
 
-    set_ctdb_variables
+    case $init_style in
+       suse)
+           set_retval $RETVAL
+           rc_status -v
+           ;;
+       redhat)
+           [ $RETVAL -eq 0 ] && success || failure
+           echo
+           ;;
+    esac
 
     return $RETVAL
-}      
+}
 
 stop() {
     echo -n $"Shutting down ctdbd service: "
@@ -197,12 +327,17 @@ stop() {
     while pkill -0 -f $ctdbd ; do
        sleep 1
        count=$(($count + 1))
-       [ $count -gt 10 ] && {
+       [ $count -gt 30 ] && {
            echo -n $"killing ctdbd "
            pkill -9 -f $ctdbd
            pkill -9 -f $CTDB_BASE/events.d/
        }
     done
+    # make sure all ips are dropped, pfkill -9 might leave them hanging around
+    drop_all_public_ips
+
+    rm -f "$pidfile"
+
     case $init_style in
        suse)
            # re-set the return code to the recorded RETVAL in order
@@ -211,40 +346,66 @@ stop() {
            rc_status -v
            ;;
        redhat)
-           echo
+            [ $RETVAL -eq 0 ] && success || failure
            [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/ctdb
            echo ""
            ;;
     esac
+
     return $RETVAL
 }
 
 restart() {
     stop
     start
-}      
+}
 
-status() {
-    echo -n $"Checking for ctdbd service: "
-    ctdb ping >/dev/null 2>&1 || {
-       RETVAL=$?
-       echo -n "  ctdbd not running. "
-       case $init_style in
-           suse)
-               set_retval $RETVAL
-               rc_status -v
-               ;;
-           redhat)
-               echo ""
-               ;;
-       esac
-       return $RETVAL
-    }
-    echo ""
-    ctdb status
+# Given that CTDB_VALGRIND is a debug option we don't support the pid
+# file.  We just do a quick and dirty hack instead.  Otherwise we just
+# end up re-implementing each distro's pidfile support...
+check_status_valgrind ()
+{
+    if pkill -0 -f "valgrind.*${ctdbd}" ; then
+       echo "ctdbd is running under valgrind..."
+       return 0
+    else
+       echo "ctdbd is not running"
+       return 1
+    fi
+}
+
+check_status ()
+{
+    # Backward compatibility.  When we arrange to pass --pidfile to
+    # ctdbd we also create the directory that will contain it.  If
+    # that directory is missing then we don't use the pidfile to check
+    # status.
+    if [ -d $(dirname "$pidfile") ] ; then
+       _pf_opt="-p $pidfile"
+    else
+       _pf_opt=""
+    fi
+
+    case "$init_style" in
+       valgrind)
+           check_status_valgrind
+           ;;
+       suse)
+           checkproc $_pf_opt "$ctdbd"
+           rc_status -v
+           ;;
+       redhat)
+           status $_pf_opt -l "ctdb" "$ctdbd"
+           ;;
+       debian)
+           status_of_proc $_pf_opt "$ctdbd" "ctdb"
+           ;;
+    esac
 }
 
 
+[ -x "$CTDB_BASE/rc.ctdb" ] && "$CTDB_BASE/rc.ctdb" $1
+
 case "$1" in
     start)
        start
@@ -252,21 +413,23 @@ case "$1" in
     stop)
        stop
        ;;
-    restart|reload)
+    restart|reload|force-reload)
        restart
        ;;
     status)
-       status
+       check_status
        ;;
-    condrestart)
-       ctdb status > /dev/null && restart || :
+    condrestart|try-restart)
+       if check_status >/dev/null ; then
+           restart
+       fi
        ;;
     cron)
        # used from cron to auto-restart ctdb
-       ctdb status > /dev/null || restart
+       check_status >/dev/null || restart
        ;;
     *)
-       echo $"Usage: $0 {start|stop|restart|status|cron|condrestart}"
+       echo $"Usage: $0 {start|stop|restart|reload|force-reload|status|cron|condrestart|try-restart}"
        exit 1
 esac