From b291029e8661dfc2f03118921e854eec4e5bbb75 Mon Sep 17 00:00:00 2001 From: Tony Hutter Date: Fri, 10 Feb 2017 16:09:45 -0800 Subject: [PATCH] Enclosure LED fixes - Pass $VDEV_ENC_SYSFS_PATH to 'zpool [iostat|status] -c' to include enclosure LED sysfs path. - Set LEDs correctly after import. This includes clearing any erroniously set LEDs prior to the import, and setting the LED for any UNAVAIL drives. - Include symlink for vdev_attach-led.sh in Makefile.am. - Print the VDEV path in all-syslog.sh, and fix it so the pool GUID actually prints. Reviewed-by: Don Brady Reviewed-by: Brian Behlendorf Signed-off-by: Tony Hutter Closes #5716 Closes #5751 --- cmd/zed/Makefile.am | 8 +- cmd/zed/zed.d/all-syslog.sh | 3 +- cmd/zed/zed.d/pool_import-led.sh | 1 + cmd/zed/zed.d/statechange-led.sh | 171 +++++++++++++++++++++++++------ cmd/zed/zed.d/zed-functions.sh | 22 ++++ cmd/zpool/zpool_iter.c | 17 ++- cmd/zpool/zpool_util.h | 1 + man/man8/zpool.8 | 32 +++--- 8 files changed, 204 insertions(+), 51 deletions(-) create mode 120000 cmd/zed/zed.d/pool_import-led.sh diff --git a/cmd/zed/Makefile.am b/cmd/zed/Makefile.am index 1fe2047c0..97733a512 100644 --- a/cmd/zed/Makefile.am +++ b/cmd/zed/Makefile.am @@ -67,7 +67,9 @@ dist_zedexec_SCRIPTS = \ zed.d/scrub_finish-notify.sh \ zed.d/statechange-led.sh \ zed.d/statechange-notify.sh \ - zed.d/vdev_clear-led.sh + zed.d/vdev_clear-led.sh \ + zed.d/vdev_attach-led.sh \ + zed.d/pool_import-led.sh zedconfdefaults = \ all-syslog.sh \ @@ -76,7 +78,9 @@ zedconfdefaults = \ scrub_finish-notify.sh \ statechange-led.sh \ statechange-notify.sh \ - vdev_clear-led.sh + vdev_clear-led.sh \ + vdev_attach-led.sh \ + pool_import-led.sh install-data-hook: $(MKDIR_P) "$(DESTDIR)$(zedconfdir)" diff --git a/cmd/zed/zed.d/all-syslog.sh b/cmd/zed/zed.d/all-syslog.sh index 5a3c8ad4d..68d3cf360 100755 --- a/cmd/zed/zed.d/all-syslog.sh +++ b/cmd/zed/zed.d/all-syslog.sh @@ -6,6 +6,7 @@ . "${ZED_ZEDLET_DIR}/zed-functions.sh" zed_log_msg "eid=${ZEVENT_EID}" "class=${ZEVENT_SUBCLASS}" \ - "${ZEVENT_POOL:+"pool=${ZEVENT_POOL}"}" \ + "${ZEVENT_POOL_GUID:+"pool_guid=${ZEVENT_POOL_GUID}"}" \ + "${ZEVENT_VDEV_PATH:+"vdev_path=${ZEVENT_VDEV_PATH}"}" \ "${ZEVENT_VDEV_STATE_STR:+"vdev_state=${ZEVENT_VDEV_STATE_STR}"}" exit 0 diff --git a/cmd/zed/zed.d/pool_import-led.sh b/cmd/zed/zed.d/pool_import-led.sh new file mode 120000 index 000000000..7d7404398 --- /dev/null +++ b/cmd/zed/zed.d/pool_import-led.sh @@ -0,0 +1 @@ +statechange-led.sh \ No newline at end of file diff --git a/cmd/zed/zed.d/statechange-led.sh b/cmd/zed/zed.d/statechange-led.sh index d9f7c89f7..e2dc19b72 100755 --- a/cmd/zed/zed.d/statechange-led.sh +++ b/cmd/zed/zed.d/statechange-led.sh @@ -2,13 +2,19 @@ # # Turn off/on the VDEV's enclosure fault LEDs when the pool's state changes. # -# Turn LED on if the VDEV becomes faulted or degraded, and turn it back off -# when it's online again. It will also turn on the LED (or keep it on) if -# the drive becomes unavailable, unless the drive was in was a previously -# online state (online->unavail is a normal state transition during an -# autoreplace). +# Turn the VDEV's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL. +# Turn the LED off when it's back ONLINE again. # -# This script requires that your enclosure be supported by the +# This script run in two basic modes: +# +# 1. If $ZEVENT_VDEV_ENC_SYSFS_PATH and $ZEVENT_VDEV_STATE_STR are set, then +# only set the LED for that particular VDEV. This is the case for statechange +# events and some vdev_* events. +# +# 2. If those vars are not set, then check the state of all VDEVs in the pool,i +# and set the LEDs accordingly. This is the case for pool_import events. +# +# Note that this script requires that your enclosure be supported by the # Linux SCSI enclosure services (ses) driver. The script will do nothing # if you have no enclosure, or if your enclosure isn't supported. # @@ -16,8 +22,8 @@ # 0: enclosure led successfully set # 1: enclosure leds not not available # 2: enclosure leds administratively disabled -# 3: ZED didn't pass enclosure sysfs path -# 4: Enclosure sysfs path doesn't exist +# 3: The led sysfs path passed from ZFS does not exist +# 4: $ZPOOL not set [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" @@ -30,15 +36,54 @@ if [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then exit 2 fi -[ -n "${ZEVENT_VDEV_ENC_SYSFS_PATH}" ] || exit 3 +zed_check_cmd "$ZPOOL" || exit 4 + +# Global used in set_led debug print +vdev="" + +# set_led (file) +# +# Write an enclosure sysfs file +# +# Arguments +# file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault) +# val: value to set it to +# +# Globals +# vdev: VDEV name for debug printing +# +# Return +# nothing +# +function set_led { + file="$1" + val="$2" -[ -e "${ZEVENT_VDEV_ENC_SYSFS_PATH}/fault" ] || exit 4 + # Set the value twice. I've seen enclosures that were + # flakey about setting it the first time. + echo "$val" > "$file" + echo "$val" > "$file" + zed_log_msg "vdev $vdev set '$file' LED to $val" +} -# Turn on/off enclosure LEDs -function led +# check_and_set_led (file, val) +# +# Read an enclosure sysfs file, and write it if it's not already set to 'val' +# +# Arguments +# file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault) +# val: value to set it to +# +# Return +# 0 on success, 3 on missing sysfs path +# +function check_and_set_led { - file="$1/fault" - val=$2 + file="$1" + val="$2" + if [ ! -e "$ZEVENT_VDEV_ENC_SYSFS_PATH/fault" ] ; then + return 3 + fi # We want to check the current state first, since writing to the # 'fault' entry always always causes a SES command, even if the @@ -53,28 +98,88 @@ function led fi if [ "$current" != "$val" ] ; then - # Set the value twice. I've seen enclosures that were - # flakey about setting it the first time. - echo "$val" > "$file" - echo "$val" > "$file" + set_led "$file" "$val" fi } -# Decide whether to turn on/off an LED based on the state -# Pass in path name and fault string ("ONLINE"/"FAULTED"/"DEGRADED"...etc) -# -# We only turn on LEDs when a drive becomes FAULTED, DEGRADED, or UNAVAIL and -# only turn it on when it comes back ONLINE. All other states are ignored, and -# keep the previous LED state. -function process { - path="$1" - fault=$2 - if [ "$fault" == "FAULTED" ] || [ "$fault" == "DEGRADED" ] || \ - [ "$fault" == "UNAVAIL" ] ; then - led "$path" 1 - elif [ "$fault" == "ONLINE" ] ; then - led "$path" 0 +function state_to_val { + state="$1" + if [ "$state" == "FAULTED" ] || [ "$state" == "DEGRADED" ] || \ + [ "$state" == "UNAVAIL" ] ; then + echo 1 + elif [ "$state" == "ONLINE" ] ; then + echo 0 fi } -process "$ZEVENT_VDEV_ENC_SYSFS_PATH" "$ZEVENT_VDEV_STATE_STR" +# process_pool ([pool]) +# +# Iterate through a pool (or pools) and set the VDEV's enclosure slot LEDs to +# the VDEV's state. +# +# Arguments +# pool: Optional pool name. If not specified, iterate though all pools. +# +# Return +# 0 on success, 3 on missing sysfs path +# +function process_pool +{ + pool="$1" + rc=0 + + # Lookup all the current LED values and paths in parallel + cmd='echo led_token=$(cat "$VDEV_ENC_SYSFS_PATH/fault"),"$VDEV_ENC_SYSFS_PATH",' + out=$($ZPOOL status -vc "$cmd" $pool | grep 'led_token=') + while read -r vdev state read write chksum therest ; do + + # Read out current LED value and path + tmp=$(echo "$therest" | sed 's/^.*led_token=//g') + IFS="," read -r current_val vdev_enc_sysfs_path <<< "$tmp" + + if [ -z "$vdev_enc_sysfs_path" ] ; then + # Skip anything with no sysfs LED entries + continue + fi + + # On some enclosures if you write 1 to fault, and read it back, + # it will return 2. Treat all non-zero values as 1 for + # simplicity. + if [ "$current_val" != "0" ] ; then + current_val=1 + fi + + val=$(state_to_val "$state") + + if [ "$current_val" == "$val" ] ; then + # LED is already set correctly + continue + fi + + if [ ! -e "$vdev_enc_sysfs_path/fault" ] ; then + rc=1 + zed_log_msg "vdev $vdev '$file/fault' doesn't exist" + continue; + fi + + set_led "$vdev_enc_sysfs_path/fault" $val + + done <<< "$out" + if [ "$rc" == "0" ] ; then + return 0 + else + # We didn't see a sysfs entry that we wanted to set + return 3 + fi +} + +if [ ! -z "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ ! -z "$ZEVENT_VDEV_STATE_STR" ] ; then + # Got a statechange for an individual VDEV + val=$(state_to_val "$ZEVENT_VDEV_STATE_STR") + + vdev="$(basename $ZEVENT_VDEV_PATH)" + check_and_set_led "$ZEVENT_VDEV_ENC_SYSFS_PATH/fault" "$val" +else + # Process the entire pool + process_pool $(zed_guid_to_pool $ZEVENT_POOL_GUID) +fi diff --git a/cmd/zed/zed.d/zed-functions.sh b/cmd/zed/zed.d/zed-functions.sh index 1ddafa99f..d234fb48a 100644 --- a/cmd/zed/zed.d/zed-functions.sh +++ b/cmd/zed/zed.d/zed-functions.sh @@ -413,3 +413,25 @@ zed_rate_limit() zed_unlock "${lockfile}" "${lockfile_fd}" return "${rv}" } + + +# zed_guid_to_pool (guid) +# +# Convert a pool GUID into its pool name (like "tank") +# Arguments +# guid: pool GUID (decimal or hex) +# +# Return +# Pool name +# +function zed_guid_to_pool +{ + if [ -z "$1" ] ; then + return + fi + + guid=$(printf "%llu" $1) + if [ ! -z "$guid" ] ; then + $ZPOOL get -H -ovalue,name guid | awk '$1=='$guid' {print $2}' + fi +} diff --git a/cmd/zpool/zpool_iter.c b/cmd/zpool/zpool_iter.c index e93dc0f89..7ce0ccf9e 100644 --- a/cmd/zpool/zpool_iter.c +++ b/cmd/zpool/zpool_iter.c @@ -332,9 +332,11 @@ vdev_run_cmd_thread(void *cb_cmd_data) char cmd[_POSIX_ARG_MAX]; /* Set our VDEV_PATH and VDEV_UPATH env vars and run command */ - if (snprintf(cmd, sizeof (cmd), "VDEV_PATH=%s && VDEV_UPATH=%s && %s", - data->path, data->upath ? data->upath : "\"\"", data->cmd) >= - sizeof (cmd)) { + if (snprintf(cmd, sizeof (cmd), "VDEV_PATH=%s && VDEV_UPATH=\"%s\" && " + "VDEV_ENC_SYSFS_PATH=\"%s\" && %s", data->path ? data->path : "", + data->upath ? data->upath : "", + data->vdev_enc_sysfs_path ? data->vdev_enc_sysfs_path : "", + data->cmd) >= sizeof (cmd)) { /* Our string was truncated */ return; } @@ -364,11 +366,15 @@ for_each_vdev_run_cb(zpool_handle_t *zhp, nvlist_t *nv, void *cb_vcdl) vdev_cmd_data_t *data; char *path = NULL; char *vname = NULL; + char *vdev_enc_sysfs_path = NULL; int i, match = 0; if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0) return (1); + nvlist_lookup_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH, + &vdev_enc_sysfs_path); + /* Spares show more than once if they're in use, so skip if exists */ for (i = 0; i < vcdl->count; i++) { if ((strcmp(vcdl->data[i].path, path) == 0) && @@ -406,6 +412,10 @@ for_each_vdev_run_cb(zpool_handle_t *zhp, nvlist_t *nv, void *cb_vcdl) data->path = strdup(path); data->upath = zfs_get_underlying_path(path); data->cmd = vcdl->cmd; + if (vdev_enc_sysfs_path) + data->vdev_enc_sysfs_path = strdup(vdev_enc_sysfs_path); + else + data->vdev_enc_sysfs_path = NULL; vcdl->count++; @@ -500,6 +510,7 @@ free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl) free(vcdl->data[i].pool); free(vcdl->data[i].upath); free(vcdl->data[i].line); + free(vcdl->data[i].vdev_enc_sysfs_path); } free(vcdl->data); free(vcdl); diff --git a/cmd/zpool/zpool_util.h b/cmd/zpool/zpool_util.h index acdfd5075..61ed33c15 100644 --- a/cmd/zpool/zpool_util.h +++ b/cmd/zpool/zpool_util.h @@ -80,6 +80,7 @@ typedef struct vdev_cmd_data char *upath; /* vdev underlying path */ char *pool; /* Pool name */ char *cmd; /* backpointer to cmd */ + char *vdev_enc_sysfs_path; /* enclosure sysfs path (if any) */ } vdev_cmd_data_t; typedef struct vdev_cmd_data_list diff --git a/man/man8/zpool.8 b/man/man8/zpool.8 index d8e33d6f2..35a195aa0 100644 --- a/man/man8/zpool.8 +++ b/man/man8/zpool.8 @@ -1548,12 +1548,16 @@ base 1024. To get the raw values, use the \fB-p\fR flag. Run a command on each vdev and include first line of output .sp The \fB-c\fR option allows you to run an arbitrary command on each vdev and -display the first line of output in zpool iostat. The environment vars -\fBVDEV_PATH\fR and \fBVDEV_UPATH\fR are set to the vdev's path and "underlying -path" before running the command. For device mapper, multipath, or partitioned -vdevs, \fBVDEV_UPATH\fR is the actual underlying /dev/sd* disk. This can be -useful if the command you're running requires a /dev/sd* device. Commands run -in parallel for each vdev for performance. +display the first line of output in zpool iostat. The following environment +vars are set before running each command: +.sp +\fB$VDEV_PATH\fR: Full path to the vdev. +.LP +\fB$VDEV_UPATH\fR: "Underlying path" to the vdev. For device mapper, multipath, or +partitioned vdevs, \fBVDEV_UPATH\fR is the actual underlying /dev/sd* disk. +This can be useful if the command you're running requires a /dev/sd* device. +.LP +\fB$VDEV_ENC_SYSFS_PATH\fR: The sysfs path to the vdev's enclosure LEDs (if any). .RE .sp @@ -2116,12 +2120,16 @@ If a scrub or resilver is in progress, this command reports the percentage done Run a command on each vdev and include first line of output .sp The \fB-c\fR option allows you to run an arbitrary command on each vdev and -display the first line of output in zpool status. The environment vars -\fBVDEV_PATH\fR and \fBVDEV_UPATH\fR are set to the vdev's path and "underlying -path" before running the command. For device mapper, multipath, or partitioned -vdevs, \fBVDEV_UPATH\fR is the actual underlying /dev/sd* disk. This can be -useful if the command you're running requires a /dev/sd* device. Commands run -in parallel for each vdev for performance. +display the first line of output in zpool iostat. The following environment +vars are set before running each command: +.sp +\fB$VDEV_PATH\fR: Full path to the vdev. +.LP +\fB$VDEV_UPATH\fR: "Underlying path" to the vdev. For device mapper, multipath, or +partitioned vdevs, \fBVDEV_UPATH\fR is the actual underlying /dev/sd* disk. +This can be useful if the command you're running requires a /dev/sd* device. +.LP +\fB$VDEV_ENC_SYSFS_PATH\fR: The sysfs path to the vdev's enclosure LEDs (if any). .RE .sp -- 2.40.0