�ɲɾ�����ӯ�����һ��ˣ��������С���˴��ͣ�������P���ҹ��ñ˽��ά�Բ��������˸߸ԣ�������ơ��ҹ��ñ�����ά�Բ���ˡ���˳^�ӣ������ӡ� ���ͯj�ӣ��ƺ���ӣ� ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!usr/local/nagios/plugins/check_raid_all000075500000056437152536020430014261 0ustar00#!/bin/bash # # check_raid_all -- universal RAID health check for Nagios / NRPE. # # Checks in a single run whatever the host actually has: # * Linux software RAID (mdadm), parsed from /proc/mdstat # * LSI MegaRAID / Dell PERC, via storcli64, perccli64 or MegaCli64 # # A host with no RAID card and no md arrays is reported OK (use -r to turn that # into CRITICAL), so the very same check can be deployed on every server. # Supersedes check_mdadm and check_megaraid_sas. # # Exit codes: 0 = OK, 1 = WARNING, 2 = CRITICAL, 3 = UNKNOWN # # The controller CLIs need root (/proc/mdstat does not) -- run this via sudo. set -u export LC_ALL=C PATH='/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin' export PATH PROGNAME=${0##*/} E_OK=0 E_WARNING=1 E_CRITICAL=2 E_UNKNOWN=3 # Return codes of the check_* functions below. R_CHECKED=0 # controller/arrays found and evaluated R_ABSENT=1 # nothing of this kind on the host R_NOANSWER=2 # tool is installed but told us nothing require_raid=0 min_arrays=0 want_hotspares=0 media_allow=0 pred_allow=0 other_allow=0 cmd_timeout=30 verbose=0 # Overridable only to make the parsers testable with recorded output. In # production sudo drops the whole environment (Defaults env_reset, the sudoers # default), so neither is reachable from the nrpe command line. If env_reset # is disabled on a platform, add CHECK_RAID_MDSTAT and CHECK_RAID_TOOLDIR to # env_delete in sudoers. mdstat_path=${CHECK_RAID_MDSTAT:-/proc/mdstat} extra_tooldir=${CHECK_RAID_TOOLDIR:-} [ -n "$extra_tooldir" ] && PATH="$extra_tooldir:$PATH" usage() { cat <&2; exit $E_UNKNOWN ;; esac done # ---------------------------------------------------------------- aggregation # Nagios severity order is not the exit-code order: OK < UNKNOWN < WARNING < # CRITICAL. Track the worst as a rank and translate back at the very end. worst_rank=0 summary='' detail='' perfdata='' rank_of() { case $1 in 0) printf 0 ;; 3) printf 1 ;; 1) printf 2 ;; 2) printf 3 ;; *) printf 1 ;; esac } escalate() { local _rank _rank=$(rank_of "$1") if [ "$_rank" -gt "$worst_rank" ]; then worst_rank=$_rank fi return 0 } add_summary() { if [ -n "$summary" ]; then summary="$summary; $1" else summary=$1 fi return 0 } add_detail() { if [ -n "$detail" ]; then detail="$detail $1" else detail=$1 fi return 0 } # ------------------------------------------------------------------- runners if command -v timeout >/dev/null 2>&1; then TIMEOUT="timeout $cmd_timeout" else TIMEOUT='' fi if [ "$(id -u)" -ne 0 ]; then SUDO='sudo -n' else SUDO='' fi # Run a controller CLI: never let it hang, never let its noise reach stdout. # stderr goes to a scratch file rather than /dev/null: run_tool runs inside # command substitutions, so a variable could not carry the error text back, # but a file can -- and it costs no second, separately timeout-bounded # invocation when the tool is wedged. rt_errfile="${TMPDIR:-/tmp}/check_raid_all.$$.stderr" trap 'rm -f "$rt_errfile"' EXIT run_tool() { # shellcheck disable=SC2086 $TIMEOUT $SUDO "$@" 2>"$rt_errfile" } # First stderr line of the most recent run_tool call, for the UNKNOWN message. mute_reason='' save_tool_error() { mute_reason=$(head -1 "$rt_errfile" 2>/dev/null) } # Find the first existing binary out of the names given. find_tool() { local _name _path _dir for _name in "$@"; do for _dir in "$extra_tooldir" /opt/MegaRAID/storcli /opt/MegaRAID/perccli /opt/MegaRAID/MegaCli \ /opt/storcli /opt/perccli /opt/MegaCli /usr/local/sbin /usr/sbin /usr/bin; do [ -n "$_dir" ] || continue if [ -x "$_dir/$_name" ]; then printf '%s\n' "$_dir/$_name" return 0 fi done _path=$(command -v "$_name" 2>/dev/null) if [ -n "$_path" ] && [ -x "$_path" ]; then printf '%s\n' "$_path" return 0 fi done return 1 } # -------------------------------------------------------------- software RAID # Flatten /proc/mdstat into one pipe-separated record per array: # name|state|personality|have|want|map|faulty|spare|action|percent MDSTAT_AWK=' function emit() { if (name == "") return printf "%s|%s|%s|%s|%s|%s|%d|%d|%s|%s\n", name, state, pers, have, want, map, faulty, spare, action, pct } /^md[^ \t]*[ \t]*:/ { emit() name = $1 state = $3 pers = ($4 ~ /^\(/) ? $5 : $4 if (pers ~ /\[/) pers = "" # inactive array: no personality, just members have = ""; want = ""; map = ""; faulty = 0; spare = 0; action = ""; pct = "" for (i = 3; i <= NF; i++) { if ($i ~ /\(F\)/) faulty++ if ($i ~ /\(S\)/) spare++ } next } name != "" { if (match($0, /\[[0-9]+\/[0-9]+\]/)) { split(substr($0, RSTART + 1, RLENGTH - 2), a, "/") have = a[1]; want = a[2] } if (match($0, /\[[U_]+\]/)) map = substr($0, RSTART + 1, RLENGTH - 2) if ($0 ~ /=DELAYED/) { action = "delayed" } else if ($0 ~ /=PENDING/) { action = "pending" } else if ($0 ~ /(recovery|resync|reshape|check)[ \t]*=/) { if (match($0, /recovery|resync|reshape|check/)) action = substr($0, RSTART, RLENGTH) if (match($0, /[0-9]+\.[0-9]+%/)) pct = substr($0, RSTART, RLENGTH) } } END { emit() } ' check_md() { local _records='' if [ -r "$mdstat_path" ]; then _records=$(awk "$MDSTAT_AWK" "$mdstat_path" 2>/dev/null) fi if [ -z "$_records" ]; then # No arrays at all. That is only a problem if we were told to expect some # -- which is exactly the case a bare mdstat parser reports as OK. if [ "$min_arrays" -gt 0 ]; then escalate $E_CRITICAL add_summary "mdadm: no arrays present, $min_arrays expected" perfdata="$perfdata md_arrays=0 md_failed=0 md_missing=$min_arrays md_degraded=0" return $R_CHECKED fi return $R_ABSENT fi local _total=0 _bad=0 _warn=0 _faulty=0 _spare=0 _msg='' local name state pers have want map faulty spare action pct local _state _note _desc while IFS='|' read -r name state pers have want map faulty spare action pct; do [ -n "$name" ] || continue _total=$((_total + 1)) _faulty=$((_faulty + faulty)) _spare=$((_spare + spare)) _state=ok _note='' if [ "$state" != 'active' ]; then # inactive/unknown: the array is not serving data at all _state=crit _note="state=$state" elif [ "${map#*_}" != "$map" ] || { [ -n "$want" ] && [ "$have" -lt "$want" ]; }; then # missing member(s) -- only a WARNING while it is being rebuilt case $action in recovery | reshape | resync) _state=warn _note="degraded, $action ${pct:-in progress}" ;; *) _state=crit _note='degraded' ;; esac elif [ "$faulty" -gt 0 ]; then # optimal map but a member is still flagged faulty (spare took over) _state=warn _note="$faulty faulty member(s)" else case $action in recovery | reshape | resync) _state=warn _note="$action ${pct:-in progress}" ;; delayed | pending) _state=warn _note="resync $action" ;; check) # routine monthly scrub, not a fault _note="check ${pct:-in progress}" ;; esac fi case $_state in crit) _bad=$((_bad + 1)) escalate $E_CRITICAL ;; warn) _warn=$((_warn + 1)) escalate $E_WARNING ;; esac _desc=$name [ -n "$pers" ] && _desc="$_desc $pers" [ -n "$want" ] && _desc="$_desc $have/$want" [ -n "$map" ] && _desc="$_desc [$map]" [ -n "$_note" ] && _desc="$_desc: $_note" if [ "$_state" != 'ok' ]; then _msg="$_msg ($_desc)" elif [ "$verbose" -eq 1 ]; then add_detail "($_desc)" fi done < exit code vd_severity() { case $1 in Optl* | Optimal*) printf %s $E_OK ;; Pdgd* | Partially*) printf %s $E_WARNING ;; Dgrd* | Degraded* | OfLn* | Offln* | Offline* | Failed*) printf %s $E_CRITICAL ;; *) printf %s $E_UNKNOWN ;; esac } # storcli/perccli physical-drive state -> exit code pd_severity() { case $1 in Onln | Online | JBOD | UGood | UGUnsp | GHS | DHS) printf %s $E_OK ;; Rbld | Cpybck | UBad | UBUnsp | Shld | SntAs) printf %s $E_WARNING ;; Offln | Failed | Msng | Missing | Bad) printf %s $E_CRITICAL ;; *) printf %s $E_UNKNOWN ;; esac } # MegaCli firmware state -> exit code fw_severity() { case $1 in Online* | JBOD* | Hotspare* | 'Unconfigured(good)'*) printf %s $E_OK ;; Rebuild* | Copyback* | 'Unconfigured(bad)'* | Shield*) printf %s $E_WARNING ;; Failed* | Offline* | Missing*) printf %s $E_CRITICAL ;; *) printf %s $E_UNKNOWN ;; esac } # Common tail for both hardware paths: error counters and hotspare expectation. report_hw_counters() { local _media=$1 _pred=$2 _other=$3 _smart=$4 _hotspares=$5 local _text='' if [ $((_media + _pred + _other + _smart)) -gt 0 ]; then _text="errors: media=$_media predictive=$_pred other=$_other" [ "$_smart" -gt 0 ] && _text="$_text smart_alerts=$_smart" if [ "$_media" -gt "$media_allow" ] || [ "$_pred" -gt "$pred_allow" ] \ || [ "$_other" -gt "$other_allow" ] || [ "$_smart" -gt 0 ]; then escalate $E_WARNING fi fi if [ "$want_hotspares" -gt 0 ]; then [ -n "$_text" ] && _text="$_text, " _text="${_text}hotspares: $_hotspares of $want_hotspares" if [ "$_hotspares" -lt "$want_hotspares" ]; then escalate $E_WARNING fi fi [ -n "$_text" ] && add_summary "$_text" perfdata="$perfdata media_err=$_media pred_err=$_pred other_err=$_other hotspares=$_hotspares" return 0 } # --------------------------------------------------------- storcli / perccli check_storcli() { local _tool=$1 local _label=${1##*/} local _out _ctrl _out=$(run_tool "$_tool" show ctrlcount nolog) if [ -z "$_out" ]; then save_tool_error return $R_NOANSWER fi # storcli changed the ctrlcount wording between releases: 007.16xx prints # "Number of Controllers = N", 007.37xx prints "Controller Count = N". _ctrl=$(printf '%s\n' "$_out" | awk -F= '/Number of Controllers|Controller Count/ { gsub(/[^0-9]/, "", $2); print $2; exit }') is_num "${_ctrl:-}" || return $R_NOANSWER [ "$_ctrl" -gt 0 ] || return $R_ABSENT _out=$(run_tool "$_tool" /call show nolog) if [ -z "$_out" ]; then save_tool_error return $R_NOANSWER fi local _vd_total=0 _vd_bad=0 _pd_total=0 _pd_bad=0 _hotspares=0 _msg='' local _ctl _id _state _sev # Virtual drives, e.g. "0/0 RAID1 Optl RW Yes RWBD - ON 446.625 GB" while read -r _ctl _id _state; do [ -n "$_id" ] || continue _vd_total=$((_vd_total + 1)) _sev=$(vd_severity "$_state") if [ "$_sev" -ne $E_OK ]; then _vd_bad=$((_vd_bad + 1)) escalate "$_sev" _msg="$_msg [c$_ctl]VD$_id=$_state" elif [ "$verbose" -eq 1 ]; then add_detail "[c$_ctl]VD$_id=$_state" fi done </dev/null 2>&1 || return 1 # Same hang guard as run_tool: a wedged PCI enumeration must end in this # check's own UNKNOWN, not in nrpe's command_timeout. All matching cards # are reported, joined with " + ", each stripped of PCI ids and revision. # shellcheck disable=SC2086 $TIMEOUT lspci -nn 2>/dev/null | awk ' /RAID bus controller \[0104\]/ { sub(/^[^:]*: /, "") sub(/ *\[[0-9a-f][0-9a-f][0-9a-f][0-9a-f]:[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\].*/, "") sub(/ *\(rev .*/, "") cards = cards (cards ? " + " : "") $0 } END { if (cards) print cards }' } # ---------------------------------------------------------------------- main check_md hw_rc=$R_ABSENT mute_tool='' hw_tool=$(find_tool storcli64 storcli perccli64 perccli) if [ -n "${hw_tool:-}" ]; then check_storcli "$hw_tool" hw_rc=$? [ "$hw_rc" -eq $R_NOANSWER ] && mute_tool=${hw_tool##*/} fi # storcli may be present but unusable (old firmware, wrong flavour) -- MegaCli # is still worth a try before giving up on the hardware side. if [ "$hw_rc" -ne $R_CHECKED ]; then mega_tool=$(find_tool MegaCli64 MegaCli megacli) if [ -n "${mega_tool:-}" ]; then check_megacli "$mega_tool" case $? in "$R_CHECKED") hw_rc=$R_CHECKED; mute_tool='' ;; # Keep both names when storcli was mute too -- the message must not # hide that the first tool also failed. "$R_NOANSWER") mute_tool="${mute_tool:+$mute_tool+}${mega_tool##*/}" ;; esac fi fi if [ -n "$mute_tool" ]; then # The tool is installed but gave us nothing: missing driver, no permission # or a hung controller. Never silently pass this off as healthy. escalate $E_UNKNOWN add_summary "$mute_tool: no usable answer from the controller${mute_reason:+ [$mute_reason]} (root required? driver loaded?)" fi if [ -z "$summary" ]; then pci_card=$(pci_raid_controller) if [ -n "${pci_card:-}" ]; then escalate $E_UNKNOWN add_summary "RAID controller present (${pci_card}) but no storcli/perccli/MegaCli installed" elif [ "$require_raid" -eq 1 ]; then escalate $E_CRITICAL add_summary 'no RAID controller and no mdadm array found' else add_summary 'no RAID configured on this host' fi fi case $worst_rank in 0) status='OK'; exitcode=$E_OK ;; 1) status='UNKNOWN'; exitcode=$E_UNKNOWN ;; 2) status='WARNING'; exitcode=$E_WARNING ;; *) status='CRITICAL'; exitcode=$E_CRITICAL ;; esac out="$status - $summary" [ "$verbose" -eq 1 ] && [ -n "$detail" ] && out="$out [$detail]" if [ -n "$perfdata" ]; then printf '%s |%s\n' "$out" "$perfdata" else printf '%s\n' "$out" fi exit "$exitcode"