summaryrefslogtreecommitdiffstats
path: root/cmd/zed/zed.d/io-spare.sh
blob: 9667dedcb7ca28de2c3c9f3c17c890d2eb8ef0d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/bin/sh
#
# Replace a device with a hot spare in response to IO or CHECKSUM errors.
# The following actions will be performed automatically when the number
# of errors exceed the limit set by ZED_SPARE_ON_IO_ERRORS or
# ZED_SPARE_ON_CHECKSUM_ERRORS.
#
# 1) FAULT the device on IO errors, no futher IO will be attempted.
#    DEGRADE the device on checksum errors, the device is still
#    functional and can be used to service IO requests.
# 2) Set the SES fault beacon for the device.
# 3) Replace the device with a hot spare if any are available.
#
# Once the hot sparing operation is complete either the failed device or
# the hot spare must be manually retired using the 'zpool detach' command.
# The 'autoreplace' functionality which would normally take care of this
# under Illumos has not yet been implemented.
#
# Full support for autoreplace is planned, but it requires that the full
# ZFS Diagnosis Engine be ported.  In the meanwhile this script provides
# the majority of the expected hot spare functionality.
#
# Exit codes:
#   0: hot spare replacement successful
#   1: hot spare device not available
#   2: hot sparing disabled or threshold not reached
#   3: device already faulted or degraded
#   9: internal error

[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
. "${ZED_ZEDLET_DIR}/zed-functions.sh"

# Disabled by default.  Enable in the zed.rc file.
: "${ZED_SPARE_ON_CHECKSUM_ERRORS:=0}"
: "${ZED_SPARE_ON_IO_ERRORS:=0}"


# query_vdev_status (pool, vdev)
#
# Given a [pool] and [vdev], return the matching vdev path & status on stdout.
#
# Warning: This function does not handle the case of [pool] or [vdev]
# containing whitespace.  Beware of ShellCheck SC2046.  Caveat emptor.
#
# Arguments
#   pool: pool name
#   vdev: virtual device name
#
# StdOut
#   arg1: vdev pathname
#   arg2: vdev status
#
query_vdev_status()
{
    local pool="$1"
    local vdev="$2"
    local t

    vdev="$(basename -- "${vdev}")"
    ([ -n "${pool}" ] && [ -n "${vdev}" ]) || return
    t="$(printf '\t')"

    "${ZPOOL}" status "${pool}" 2>/dev/null | sed -n -e \
        "s,^[ $t]*\(.*${vdev}\(-part[0-9]\+\)\?\)[ $t]*\([A-Z]\+\).*,\1 \3,p" \
        | tail -1
}


# main
#
# Arguments
#   none
#
# Return
#   see above
#
main()
{
    local num_errors
    local action
    local lockfile
    local vdev_path
    local vdev_status
    local spare
    local zpool_err
    local zpool_rv
    local rv

    # Avoid hot-sparing a hot-spare.
    #
    # Note: ZEVENT_VDEV_PATH is not defined for ZEVENT_VDEV_TYPE=spare.
    #
    [ "${ZEVENT_VDEV_TYPE}" = "spare" ] && exit 2

    [ -n "${ZEVENT_POOL}" ] || exit 9
    [ -n "${ZEVENT_VDEV_GUID}" ] || exit 9
    [ -n "${ZEVENT_VDEV_PATH}" ] || exit 9

    zed_check_cmd "${ZPOOL}" "${ZINJECT}" || exit 9

    # Fault the device after a given number of I/O errors.
    #
    if [ "${ZEVENT_SUBCLASS}" = "io" ]; then
        if [ "${ZED_SPARE_ON_IO_ERRORS}" -gt 0 ]; then
            num_errors=$((ZEVENT_VDEV_READ_ERRORS + ZEVENT_VDEV_WRITE_ERRORS))
            [ "${num_errors}" -ge "${ZED_SPARE_ON_IO_ERRORS}" ] \
                && action="fault"
        fi 2>/dev/null

    # Degrade the device after a given number of checksum errors.
    #
    elif [ "${ZEVENT_SUBCLASS}" = "checksum" ]; then
        if [ "${ZED_SPARE_ON_CHECKSUM_ERRORS}" -gt 0 ]; then
            num_errors="${ZEVENT_VDEV_CKSUM_ERRORS}"
            [ "${num_errors}" -ge "${ZED_SPARE_ON_CHECKSUM_ERRORS}" ] \
                && action="degrade"
        fi 2>/dev/null

    else
        zed_log_err "unsupported event class \"${ZEVENT_SUBCLASS}\""
        exit 9
    fi

    # Error threshold not reached.
    #
    if [ -z "${action}" ]; then
        exit 2
    fi

    lockfile="zed.spare.lock"
    zed_lock "${lockfile}"

    # shellcheck disable=SC2046
    set -- $(query_vdev_status "${ZEVENT_POOL}" "${ZEVENT_VDEV_PATH}")
    vdev_path="$1"
    vdev_status="$2"

    # Device is already FAULTED or DEGRADED.
    #
    if [ "${vdev_status}" = "FAULTED" ] \
            || [ "${vdev_status}" = "DEGRADED" ]; then
        rv=3

    else
        rv=1

        # 1) FAULT or DEGRADE the device.
        #
        "${ZINJECT}" -d "${ZEVENT_VDEV_GUID}" -A "${action}" "${ZEVENT_POOL}"

        # 2) Set the SES fault beacon.
        #
        # TODO: Set the 'fault' or 'ident' beacon for the device.  This can
        # be done through the sg_ses utility.  The only hard part is to map
        # the sd device to its corresponding enclosure and slot.  We may
        # be able to leverage the existing vdev_id scripts for this.
        #
        # $ sg_ses --dev-slot-num=0 --set=ident /dev/sg3
        # $ sg_ses --dev-slot-num=0 --clear=ident /dev/sg3

        # 3) Replace the device with a hot spare.
        #
        # Round-robin through the spares trying those that are available.
        #
        for spare in ${ZEVENT_VDEV_SPARE_PATHS}; do

            # shellcheck disable=SC2046
            set -- $(query_vdev_status "${ZEVENT_POOL}" "${spare}")
            vdev_path="$1"
            vdev_status="$2"

            [ "${vdev_status}" = "AVAIL" ] || continue

            zpool_err="$("${ZPOOL}" replace "${ZEVENT_POOL}" \
                "${ZEVENT_VDEV_GUID}" "${vdev_path}" 2>&1)"; zpool_rv=$?

            if [ "${zpool_rv}" -ne 0 ]; then
                [ -n "${zpool_err}" ] && zed_log_err "zpool ${zpool_err}"
            else
                rv=0
                break
            fi
        done
    fi

    zed_unlock "${lockfile}"
    exit "${rv}"
}


main "$@"