Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Still unmounts share during transfers... #7

Open
omeletteo opened this issue May 31, 2014 · 1 comment
Open

Still unmounts share during transfers... #7

omeletteo opened this issue May 31, 2014 · 1 comment

Comments

@omeletteo
Copy link

Just before banishing nfs_automount from my systems once and for all, I thought I'd check to see if anyone had fixed this problem. It's disappointing to see that almost one year since I posted about this bug, nothing has been done about it. As far as I'm concerned, it makes nfs_automount practically unusable.

In case it's of any help to anyone, the below includes the changes I made to the original back then in an attempt to fix it. While it fails to do so, it at least alleviates the problem somewhat. I try monitoring transfer-bandwidth and using the speeds detected to differentiate between a busy and a missing remote-share.

Frankly, I can't really see any advantage to using NFS. Samba delivers practically the same transfer speeds with none of the system-hangs NFS is infamous for.

!/usr/bin/env bash

Distributed under MIT license

Copyright (c) 2013 Ville Walveranta

http://my.galagzee.com

Absolute path to the configuration file

(if empty or not defined, the default is

'nfs_automount.conf' in the script's directory)

CONFIG_FILE=/etc/nfs_automount.conf

IF="wlan0" # this really need to be set 'programmatically' - how?
RXPREV=-1
TXPREV=-1
RXMAXBW=0
TXMAXBW=0
BWRX=0
BWTX=0
oldT="$(date +%s) - INTERVAL"
skip_count=-1

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/sbin:/usr/sbin:/usr/bin:/root/bin
export PATH

-- DATESTAMP AT RUNTIME --

DATESTAMP=date --rfc-3339=seconds

-- IMPORT CONFIGURATION --

if [ -z ${CONFIG_FILE} ] ; then
SCRIPT_PATH="dirname \"$0\""
SCRIPT_PATH="( cd \"$SCRIPT_PATH\" && pwd )"
CONFIG=${SCRIPT_PATH}/nfs_automount.conf
else
CONFIG=${CONFIG_FILE}
fi

if [ -f ${CONFIG} ] ; then
source ${CONFIG}
else
echo "nfs_automount [${DATESTAMP}]: [CRIT] Configuration file (${CONFIG}) missing; cannot continue!"
exit 1
fi

counter=0
declare -a MOUNTDATA
for MOUNT in ${MOUNTS[@]}; do
MOUNTDATA[((counter++))]=${MOUNT}
done

-- LOGGING --

function log {

if [ ! -z "$1" ] ; then
log_msg="$1"
else
log_msg="[WARN] Parameter missing (log)!"
fi

TAG=${log_msg:0:6}

if [ ${TAG} = "[CRIT]" ] || [ ${TAG} = "[NOTE]" ] ; then
critical=true
else
critical=false
fi

DATESTAMP=date --rfc-3339=seconds

if [ "${DEBUGLOG}" = "true" ] || [ "${critical}" = "true" ] ; then
message=echo "nfs_automount [${DATESTAMP}]: ${log_msg}"

if [ "${LOGTYPE}" = "log" ] && [ ! -z ${LOGFILEPATH} ]; then
  _output_type="echo"
  if [ -f ${LOGFILEPATH} ] ; then
    _output_type="log"
  else
     touch ${LOGFILEPATH} > /dev/null 2>&1
     if [ -f ${LOGFILEPATH} ] ; then
       _output_type="log"
     fi
  fi
  if [ "${_output_type}" = "log" ] ; then
    echo ${message} >> ${LOGFILEPATH}
  else
    echo ${message}
  fi
else
  echo ${message}
fi

fi

if [ ${TAG} = "[CRIT]" ] ; then
logger_msg=echo "nfs_automount: ${log_msg}"
logger ${logger_msg}
fi
}

-- CHECK & SET COMMAND LOCATIONS --

function check_command {
CMD=which $1 > /dev/null 2>&1
if [ $? -ne 0 ] ; then
log "[CRIT] Command $1 not found. Cannot continue."
if [ "$1" = "showmount" ] ; then
log "[INFO] Install nfs-common (Debian/Ubuntu), or nfs-utils (RedHat/CentOS) first!"
fi
exit 1
fi
_RET=${CMD}
return 0
}

check_command showmount; showmountcmd=${_RET}
check_command mountpoint; mountpointcmd=${_RET}
check_command rpcinfo; rpcinfocmd=${_RET}
check_command grep; grepcmd=${_RET}
check_command mount; mountcmd=${_RET}
check_command umount; umountcmd=${_RET}
check_command touch; touchcmd=${_RET}
check_command rm; rmcmd=${_RET}
check_command awk; awkcmd=${_RET}

-- OPERATIONS --

function get_mount_dataset {
if [ ! -z "$1" ] ; then
_mountdataset="$1"
else
log "[CRIT] Parameter missing (get_mount_dataset); process aborted!"
exit 1
fi

if [ -z ${DELIMITER} ] ; then
DELIMITER="|"
fi

_RET=(echo ${_mountdataset//$DELIMITER/ })
if [ ! -z ${_RET[4]} ] ; then
_RET[4]=${_RET[3]}/${_RET[4]}
fi

import and set rw/ro

if [ ${_RET[0]} = "rw" ]; then
_RET[0]="-o rw,${MOUNTOPTS}"
_RET[5]="rw"
else
_RET[0]="-o ro,${MOUNTOPTS}"
_RET[5]="ro"
fi
}

function check_mounted {
if [ ! -z "$1" ] ; then
_localmountpoint="$1"
else
log "[CRIT] Parameter missing (check_mounted); process aborted!"
exit 1
fi

if ${grepcmd} -qsE "^[^ ]+ ${_localmountpoint}" /proc/mounts; then
_RET=true
else
_RET=false
fi
}

function check_stale {
if [ ! -z "$1" ] ; then
_localmountpoint="$1"
else
log "[CRIT] Parameter missing (check_stale); process aborted!"
exit 1
fi

if [ ! -d "${_localmountpoint}" ] ; then
_RET=true
else
_RET=false
fi
}

function test_remotecheckfile {
if [ ! -z "$1" ] ; then
_remotecheckfile="$1"
else
_remotecheckfile=""
fi

if [ "${_remotecheckfile}" != "" ] ; then
if [ -f ${_remotecheckfile} ] ; then
_RET=true
else
_RET=false
fi
else
_RET=true
fi
}

function check_remotenfs {
if [ ! -z "$1" ] ; then
_remotesystem="$1"
else
log "[CRIT] Parameter missing (check_remotenfs); process aborted!"
exit 1
fi

read -t1 < <(${rpcinfocmd} -t ${_remotesystem} nfs 2>&-)
if [ $? -eq 0 ]; then
_RET=true
else
_RET=false
fi
}

function check_remoteshare {
if [ ! -z "$1" ] && [ ! -z $2 ] ; then
_remotesystem="$1"
_remoteshare="$2"
else
log "[CRIT] Parameter(s) missing (check_remoteshare); process aborted!"
exit 1
fi

remotesharecheck=${showmountcmd} -e ${_remotesystem} | ${awkcmd} '{print $1}' | ${grepcmd} "${_remoteshare}"
if [ "${remotesharecheck}" != "" ] ; then
_RET=true
else
_RET=false
fi
}

function check_transferinprogress {

T="$(date +%s)"
elapsed_T="$((T-oldT))"
let oldT=$T

RX=cat /sys/class/net/${IF}/statistics/rx_bytes
TX=cat /sys/class/net/${IF}/statistics/tx_bytes

if [ $RXPREV -ne -1 ] ; then
let BWRX=($RX-$RXPREV)/$elapsed_T*$INTERVAL
let BWTX=($TX-$TXPREV)/$elapsed_T*$INTERVAL
fi

let RXPREV=$RX # set here to check against 4/5 of highest measured bandwidth, but
let TXPREV=$TX # too many false-positives, so probably needs to be 2/3 -> 1/2

if [ $BWRX -gt $((RXMAXBW-RXMAXBW/3)) ] || [ $BWTX -gt $((TXMAXBW-TXMAXBW/3)) ] ; then
BFLG=true
else BFLG=false
fi

if [ $BWRX -gt $RXMAXBW ] ; then
let RXMAXBW=$BWRX
fi
if [ $BWTX -gt $TXMAXBW ] ; then
let TXMAXBW=$BWTX
fi

echo 'RX Max BW = '$((RXMAXBW/INTERVAL))'B/s TX Max BW = '$((TXMAXBW/INTERVAL))'B/s Current BW = ('$((BWRX/INTERVAL)) $((BWTX/INTERVAL))')'
echo $elapsed_T $skip_count
}

function valid_for_mount {
if [ ! -z "$1" ] ; then
_localmountpoint="$1"
else
log "[CRIT] Parameter missing (valid_for_mount); process aborted!"
exit 1
fi

if [ -d "${_localmountpoint}" ] ; then
if ! ${mountpointcmd} -q "${_localmountpoint}" ; then
_RET=true
else
_RET=false
fi
else
_RET=false
fi
}

function test_rw {
if [ ! -z "$1" ] ; then
_rw_testfile="$1"
else
_rw_testfile="nfs_automount_rw_test.Kk6MC2CkHoinbSE3CYqv"
fi

${touchcmd} ${_rw_testfile} > /dev/null 2>&1
if [ -f ${_rw_testfile} ] ; then
${rmcmd} -f ${_rw_testfile}
_RET=true
else
_RET=false
fi
}

function nfs_umount {
if [ ! -z "$1" ] ; then
_localmountpoint="$1"
else
log "[CRIT] Parameter missing (nfs_umount); process aborted!"
exit 1
fi

check_mounted "${_localmountpoint}"
if ${_RET} ; then

${umountcmd} -f -l "${_localmountpoint}"
check_mounted "${_localmountpoint}"
if ${_RET} ; then
  _RET=false
else
  _RET=true
fi

else
_RET=true
fi

}

function nfs_mount {
if [ ! -z "$1" ] && [ ! -z "$2" ] && [ ! -z "$3" ] && [ ! -z "$4" ] ; then
_mountopts="$1"
_remotesystem="$2"
_remoteshare="$3"
_localmountpoint="$4"
else
log "[CRIT] Parameter(s) missing (nfs_mount); process aborted!"
exit 1
fi

Make sure remote NFS service is available

check_remotenfs ${_remotesystem}
if ${_RET} ; then

# Make sure the remote NFS share is available
check_remoteshare ${_remotesystem} ${_remoteshare}
if ${_RET} ; then

  # Make sure the local mountpoint exists and is free
  valid_for_mount ${_localmountpoint}
  if ${_RET} ; then

    log "[INFO] Attempting mount: ${mountcmd} ${_mountopts} ${_remotesystem}:${_remoteshare} ${_localmountpoint}"
    ${mountcmd} ${_mountopts} ${_remotesystem}:${_remoteshare} ${_localmountpoint}
    if [ $? -ne 0 ] ; then
      log "[CRIT] Unable to mount share '${_remoteshare}'!"
    else
      log "[INFO] Share '${_remoteshare}' mounted from '${_remotesystem}' at '${_localmountpoint}'."
    fi

  else
    log "[CRIT] Local mount point '${_localmountpoint}' missing or already in use!"
  fi

else
  log "[CRIT] Remote share '${_remoteshare}' unavailable!"
fi

else
log "[CRIT] Remote NFS service unavailable at '${_remotesystem}'!"
fi
}

function nfs_remount {
if [ ! -z "$1" ] && [ ! -z "$2" ] && [ ! -z "$3" ] && [ ! -z "$4" ] ; then
_mountopts="$1"
_remotesystem="$2"
_remoteshare="$3"
_localmountpoint="$4"
else
log "[CRIT] Parameter(s) missing (nfs_remount); process aborted!"
exit 1
fi

nfs_umount ${_localmountpoint}
if ${_RET} ; then
log "[INFO] Unmounted '${_localmountpoint}'. Proceeding with remount."
nfs_mount "${_mountopts}" "${_remotesystem}" "${_remoteshare}" "${_localmountpoint}"
else
log "[CRIT] Could not unmount '${_localmountpoint}'. Cannot proceed with remount!"
fi
}

-- LOGIC --

log "[NOTE] Monitoring started."

while : ; do
RXMAXBW_copy=$RXMAXBW
TXMAXBW_copy=$TXMAXBW
check_transferinprogress
if [ $skip_count -lt 0 ] || [ $BFLG == true ] ; then
skip_count=1
fi

if ! ${DEBUGLOG} ; then
log "[NOTE] Checking shares. Debug logging disabled."
fi

mountscnt=${#MOUNTDATA[@]}
for (( i=0; i<${mountscnt}; i++)); do

get_mount_dataset ${MOUNTDATA[$i]}

((datasetno=${i} + 1))

if [ ${#_RET[@]} -lt 5 ] ; then
  log "[CRIT] Incomplete mount dataset ${datasetno} in '${CONFIG}'. Skipping!"

  if [ ${#_RET[@]} -eq 1 ] ; then
    log "[NOTE] Only one parameter in dataset ${datasetno} in '${CONFIG}'. Bad delimiter ('${DELIMITER}')?"
  fi

  continue
fi

NFSMOUNTOPTS=${_RET[0]}
REMOTESYSTEM=${_RET[1]}
REMOTESHARE=${_RET[2]}
LOCALMOUNTPOINT=${_RET[3]}
REMOTECHECKFILE=${_RET[4]}
READWRITE=${_RET[5]}

check_remotenfs ${REMOTESYSTEM}
if ${_RET} ; then
  log "[INFO] (dataset ${datasetno}) Remote server/NFS service at '${REMOTESYSTEM}' available."

  check_mounted ${LOCALMOUNTPOINT}
  if ${_RET} ; then
    log "[INFO] (dataset ${datasetno}) Local mount point '${LOCALMOUNTPOINT}' active."

    check_stale ${LOCALMOUNTPOINT}
    if ! ${_RET} ; then
      log "[INFO] (dataset ${datasetno}) Local mount point '${LOCALMOUNTPOINT}' not stale."

      if [ ! -z ${REMOTECHECKFILE} ] ; then
        test_remotecheckfile ${REMOTECHECKFILE}
        if ${_RET} ; then
          log "[INFO] (dataset ${datasetno}) Remote test file '${REMOTECHECKFILE}' is reachable via local the mount point '${LOCALMOUNTPOINT}'."
        else
          log "[CRIT] (dataset ${datasetno}) Remote test file '${REMOTECHECKFILE}' could not be reached via the mount point '${LOCALMOUNTPOINT}'. This may indicate a remote system problem!"
        fi
      else
        log "[NOTE] (dataset ${datasetno}) Remote test file not defined. Skipping remote test file check for this dataset."
      fi

      # RO/RW PROCESSING  
      TESTFILE="${LOCALMOUNTPOINT}"/"${RW_TESTFILE}"
      test_rw ${TESTFILE}
      if ${_RET} ; then
        log "[INFO] (dataset ${datasetno}) Mount point '${LOCALMOUNTPOINT}' is writable."

        if [ "${READWRITE}" = "ro" ]; then
          log "[INFO] (dataset ${datasetno}) Read Only mount has been specified. Attempting remount." 
          nfs_remount "${NFSMOUNTOPTS}" "${REMOTESYSTEM}" "${REMOTESHARE}" "${LOCALMOUNTPOINT}"
        fi

      else
        log "[INFO] (dataset ${datasetno}) Mount point '${LOCALMOUNTPOINT}' is not writable."

        if [ "${READWRITE}" = "rw" ]; then
          log "[INFO] (dataset ${datasetno}) Read/Write mount has been specified. Attempting remount." 
          nfs_remount "${NFSMOUNTOPTS}" "${REMOTESYSTEM}" "${REMOTESHARE}" "${LOCALMOUNTPOINT}"
        fi

      fi

    else
      #STALE PROCESSING
      log "[CRIT] (dataset ${datasetno}) Remote NFS share '${REMOTESYSTEM}:${REMOTESHARE}' is stale; attempting remount."

      nfs_remount "${NFSMOUNTOPTS}" "${REMOTESYSTEM}" "${REMOTESHARE}" "${LOCALMOUNTPOINT}"

    fi

  else

    #UNMOUNTED PROCESSING
    log "[CRIT] (dataset ${datasetno}) Remote NFS share '${REMOTESYSTEM}:${REMOTESHARE}' is unmounted; attempting mount."

    nfs_mount "${NFSMOUNTOPTS}" "${REMOTESYSTEM}" "${REMOTESHARE}" "${LOCALMOUNTPOINT}"

  fi

else
  log "[CRIT] (dataset ${datasetno}) Remote server/NFS service at '${REMOTESYSTEM}' seems unreachable..." 
  log "[CRIT] Checking for high-bandwidth network congestion as a possible cause..."
  let skip_count=skip_count-1

  if [ $skip_count -ge 0 ] ; then
    skip_first=true
  else skip_first=false
  fi

  if [ $BFLG == true ] ; then
log "[INFO] (dataset ${datasetno}) Heavy network usage detected; skipping unmount of '$REMOTESHARE'."
  else  if [ $skip_first == true ] || ([ $RXMAXBW_copy -eq 0 ] && [ $TXMAXBW_copy -eq 0 ]) ; then
    log "[INFO] Cannot determine network usage; skipping unmount for 1 time-interval." 
  else
    #UNREACHABLE SERVER/NFS SERVICE PROCESSING
    log "[CRIT] (dataset ${datasetno}) No network congestion detected; confirming unmounted status of share '$REMOTESHARE'."

    nfs_umount "${LOCALMOUNTPOINT}"
    if ${_RET} ; then
      log "[INFO] (dataset ${datasetno}) Remote server '${REMOTESYSTEM}' or its NFS service is unreachable. Share '${REMOTESHARE}' has been confirmed unmounted!"
    else
      log "[CRIT] (dataset ${datasetno}) Remote server '${REMOTESYSTEM}' or its NFS service is unreachable. Share '${REMOTESHARE}' could not be unmounted!"
    fi
  fi
  fi
fi

done

Break here for cron style run

[[ "${RUNTYPE}" = "service" ]] || break

log "[INFO] Sleeping for ${INTERVAL} seconds."
sleep $INTERVAL

done

@dgrant
Copy link

dgrant commented Apr 18, 2015

Would be more helpful if you did a pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants