#!/bin/sh # This is an example CTDB NFS callout script for Ganesha. It is based # on the last version of 60.ganesha shipped with CTDB. As such, it # does not try to monitor RPC services that were not monitored by # 60.ganesha - this might be a useful improvement. It has also not # been properly tested. # You should check your version of NFS Ganesha to see if it ships with # a newer callout. # To use this: # # * Set CTDB_NFS_CALLOUT in your CTDB configuration to point to (a # copy of) this script, making sure it is executable. # # * Create a new directory alongside the nfs-checks.d directory, for # example nfs-checks-ganesha.d. Install 20.nfs-ganesha.check in # this directory. Symlink to any other check files from # nfs-checks.d that should still be used, such as # 00.portmapper.check. Set CTDB_NFS_CHECKS_DIR to point to this new # directory of check files. # # * It is recommended, but not required, to install the grace_period # script (usually shipped in a utils package for NFS-Ganesha) to # /usr/bin/grace_period # I (Martin Schwenke) hereby relicense all of my contributions to this # callout (and, previously, to 60.ganesha) to a license compatible # with NFS Ganesha (right now this is LGPLv3, but I'm flexible). # There may be other contributions to be considered for relicensing, # particularly those in commit 28cbe527d47822f870e8252495ab2a1c8fddd12f. ###################################################################### # Exit on 1st error set -e # Filesystem type and mount point for the (typically clustered) # volume that will contain the NFS-Ganesha state. state_fs="${CTDB_NFS_STATE_FS_TYPE:-gpfs}" state_dir="${CTDB_NFS_STATE_MNT}" # No sane default. # Location of exports file nfs_exports_file="${CTDB_NFS_EXPORTS_FILE:-/etc/ganesha/ganesha.conf}" # To change the following, edit the default values below. Do not set # these - they aren't configuration variables, just hooks for testing. nfs_service="${CTDB_NFS_SERVICE:-nfs-ganesha}" ganesha_rec_subdir=${CTDB_GANESHA_REC_SUBDIR:-.ganesha} procfs=${PROCFS_PATH:-/proc} case "$state_fs" in gpfs) GANRECDIR="/var/lib/nfs/ganesha" ;; glusterfs) if [ -z "${state_dir}" ]; then echo "CTDB_NFS_STATE_MNT not defined for GlusterFS" exit 1 fi host=$(hostname) NODESTATEDIR="$state_dir/nfs-ganesha/$host" GANSTATEDIR="$state_dir/nfs-ganesha/.noderefs" NODESTATELN="$GANSTATEDIR/$host" ;; esac ################################################## usage() { _c=$(basename "$0") cat < $_c { monitor-list-shares } EOF exit 1 } ################################################## # Basic service stop and start basic_stop() { case "$1" in nfs) service "$nfs_service" stop ;; *) usage ;; esac } basic_start() { case "$1" in nfs) service "$nfs_service" start ;; *) usage ;; esac } ################################################## # "stop" and "start" options for restarting service_stop() { case "$1" in nfs) basic_stop "nfs" ;; nlockmgr) # Do nothing - used by statd-callout : ;; *) usage ;; esac } service_start() { case "$1" in nfs) basic_start "nfs" ;; nlockmgr) # Do nothing - used by statd-callout : ;; *) usage ;; esac } ################################################## # Nitty gritty - monitoring and IP handling # Check that a symlink exists, create it otherwise. # Usage: check_ln check_ln() { if [ ! -L "${2}" ]; then rm -vrf "${2}" else _t=$(readlink "${2}") if [ "$_t" != "${1}" ]; then rm -v "${2}" fi fi # This is not an "else". It also re-creates the link if it was # removed above! if [ ! -e "${2}" ]; then ln -sfv "${1}" "${2}" fi } # Return 'active' if the shared filesystem is accessible. get_cluster_fs_state() { case "$state_fs" in gpfs) /usr/lpp/mmfs/bin/mmgetstate | awk 'NR == 4 { print $3 }' ;; glusterfs) # Since we're past create_ganesha_recdirs(), we're active. echo "active" ;; *) echo "File system $state_fs not supported" exit 1 ;; esac } create_ganesha_recdirs() { if ! _mounts=$(mount | grep "$state_fs"); then echo "Failed to find mounts of type $state_fs" exit 1 fi if [ -z "$_mounts" ]; then echo "startup $state_fs not ready" exit 0 fi case "$state_fs" in gpfs) _mntpt=$(echo "$_mounts" | sort | awk 'NR == 1 {print $3}') _link_dst="${_mntpt}/${ganesha_rec_subdir}" mkdir -vp "$_link_dst" check_ln "$_link_dst" "$GANRECDIR" ;; glusterfs) [ -d /var/lib/nfs.backup ] || mv /var/lib/nfs /var/lib/nfs.backup check_ln "$NODESTATEDIR" /var/lib/nfs mkdir -p "${NODESTATEDIR}/ganesha/v4recov" mkdir -p "${NODESTATEDIR}/ganesha/v4old" mkdir -p "${NODESTATEDIR}/statd/sm" mkdir -p "${NODESTATEDIR}/statd/sm.bak" touch "${NODESTATEDIR}/state" touch "${NODESTATEDIR}/statd/state" mkdir -p "$GANSTATEDIR" check_ln "$NODESTATEDIR" "$NODESTATELN" for _dir in "${GANSTATEDIR}/"*; do # Handle no directories case if [ ! -d "$_dir" ]; then break fi _node="${_dir##*/}" # basename if [ "${_node}" != "${host}" ]; then check_ln "${GANSTATEDIR}/${_node}/ganesha" \ "${NODESTATEDIR}/ganesha/${_node}" check_ln "${GANSTATEDIR}/${_node}/statd" \ "${NODESTATEDIR}/statd/${_node}" fi done ;; esac } service_check() { create_ganesha_recdirs # Always succeed if cluster filesystem is not active _cluster_fs_state=$(get_cluster_fs_state) if [ "$_cluster_fs_state" != "active" ]; then return 0 fi # Check that NFS Ganesha is running, according to PID file _pidfile="/var/run/ganesha/ganesha.pid" _ganesha="/usr/bin/ganesha.nfsd" if ! { read -r _pid <"$_pidfile" && grep "$_ganesha" "${procfs}/${_pid}/cmdline" } >/dev/null 2>&1; then echo "ERROR: NFS Ganesha not running according to PID file" return 1 fi return 0 } #------------------------------------------------- grace_period() { _arg="$1" if [ -x "/usr/bin/grace_period" ]; then /usr/bin/grace_period "$_arg" else dbus-send --print-reply --system --dest=org.ganesha.nfsd \ /org/ganesha/nfsd/admin org.ganesha.nfsd.admin.grace \ string:"$_arg" fi } nfs_startipreallocate() { grace_period "0:" } nfs_releaseip() { _ip="$2" # NFS-Ganesha recovery code only processes items matching $_ip grace_period "2:${_ip}" } nfs_takeip() { _ip="$2" case "$state_fs" in glusterfs) check_ln "$NODESTATEDIR" "${GANSTATEDIR}/${_ip}" ;; esac grace_period "5:${_ip}" } ################################################## # service init startup and final shutdown nfs_shutdown() { basic_stop "nfs" } nfs_startup() { basic_stop "nfs" || true create_ganesha_recdirs basic_start "nfs" _f="${procfs}/sys/net/ipv4/tcp_tw_recycle" if [ -f "$_f" ]; then echo 1 >"$_f" fi } ################################################## # list share directories nfs_monitor_list_shares() { # The 1st sed command prints anything after "Path = ", where # Path is matched case-insensitively, and must be on a word # boundary. This also deletes any semicolon-terminated items # before Path. Each output line now starts with a value for # Path, but may have other settings after a semicolon. _s1='s/.*;*[[:space:]]*\[[:space:]]*=[[:space:]]*//ip' # The 2nd sed command has 2 steps: # # a. Attempt to match an unquoted value not containing # semicolon or double-quote, followed by an optional # line-terminating semicolon or a semicolon followed by # anything else. Keep the value and double-quote it. If # the value was already quoted then the line will be # unchanged. The pattern space now starts with a # double-quoted value. _s2a='s/^\([^";][^";]*\)[[:space:]]*\(;*[[:space:]]*$\|;.*\)/"\1"/' # b. Finally, print the contents of double-quotes at the # beginning of the pattern space, discarding anything # that follows. _s2b='s/^"\([^"][^"]*\)".*/\1/p' sed -n -e "$_s1" "$nfs_exports_file" | sed -n -e "$_s2a" -e "$_s2b" } ################################################## nfs_register() { cat <