]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/zfs-tests/include/blkdev.shlib
Improve ZTS block_device_wait debugging
[FreeBSD/FreeBSD.git] / tests / zfs-tests / include / blkdev.shlib
1 #
2 # This file and its contents are supplied under the terms of the
3 # Common Development and Distribution License ("CDDL"), version 1.0.
4 # You may only use this file in accordance with the terms of version
5 # 1.0 of the CDDL.
6 #
7 # A full copy of the text of the CDDL should have accompanied this
8 # source.  A copy of the CDDL is also available via the Internet at
9 # http://www.illumos.org/license/CDDL.
10 #
11
12 #
13 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
14 # Use is subject to license terms.
15 # Copyright (c) 2012, 2016 by Delphix. All rights reserved.
16 # Copyright 2016 Nexenta Systems, Inc.
17 # Copyright (c) 2016, 2017 by Intel Corporation. All rights reserved.
18 # Copyright (c) 2017 Lawrence Livermore National Security, LLC.
19 # Copyright (c) 2017 Datto Inc.
20 # Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
21 # Copyright 2019 Richard Elling
22 #
23
24 #
25 # Returns SCSI host number for the given disk
26 #
27 function get_scsi_host #disk
28 {
29         typeset disk=$1
30         ls /sys/block/${disk}/device/scsi_device | cut -d : -f 1
31 }
32
33 #
34 # Cause a scan of all scsi host adapters by default
35 #
36 # $1 optional host number
37 #
38 function scan_scsi_hosts
39 {
40         typeset hostnum=${1}
41
42         if is_linux; then
43                 if [[ -z $hostnum ]]; then
44                         for host in /sys/class/scsi_host/host*; do
45                                 log_must eval "echo '- - -' > $host/scan"
46                         done
47                 else
48                         log_must eval \
49                             "echo /sys/class/scsi_host/host$hostnum/scan" \
50                             > /dev/null
51                         log_must eval \
52                             "echo '- - -' > /sys/class/scsi_host/host$hostnum/scan"
53                 fi
54         fi
55 }
56
57 #
58 # Wait for newly created block devices to have their minors created.
59 # Additional arguments can be passed to udevadm trigger, with the expected
60 # arguments to typically be a block device pathname. This is useful when
61 # checking waiting on a specific device to settle rather than triggering
62 # all devices and waiting for them all to settle.
63 #
64 # The udevadm settle timeout can be 120 or 180 seconds by default for
65 # some distros. If a long delay is experienced, it could be due to some
66 # strangeness in a malfunctioning device that isn't related to the devices
67 # under test. To help debug this condition, a notice is given if settle takes
68 # too long.
69 #
70 # Note: there is no meaningful return code if udevadm fails. Consumers
71 # should not expect a return code (do not call as argument to log_must)
72 #
73 function block_device_wait
74 {
75         if is_linux; then
76                 udevadm trigger $*
77                 typeset local start=$SECONDS
78                 udevadm settle
79                 typeset local elapsed=$((SECONDS - start))
80                 [[ $elapsed > 60 ]] && \
81                     log_note udevadm settle time too long: $elapsed
82         fi
83 }
84
85 #
86 # Check if the given device is physical device
87 #
88 function is_physical_device #device
89 {
90         typeset device=${1#$DEV_DSKDIR}
91         device=${device#$DEV_RDSKDIR}
92
93         if is_linux; then
94                 [[ -b "$DEV_DSKDIR/$device" ]] && \
95                 [[ -f /sys/module/loop/parameters/max_part ]]
96                 return $?
97         else
98                 echo $device | egrep "^c[0-F]+([td][0-F]+)+$" > /dev/null 2>&1
99                 return $?
100         fi
101 }
102
103 #
104 # Check if the given device is a real device (ie SCSI device)
105 #
106 function is_real_device #disk
107 {
108         typeset disk=$1
109         [[ -z $disk ]] && log_fail "No argument for disk given."
110
111         if is_linux; then
112                 lsblk $DEV_RDSKDIR/$disk -o TYPE 2>/dev/null | \
113                     egrep disk >/dev/null
114                 return $?
115         fi
116 }
117
118 #
119 # Check if the given device is a loop device
120 #
121 function is_loop_device #disk
122 {
123         typeset disk=$1
124         [[ -z $disk ]] && log_fail "No argument for disk given."
125
126         if is_linux; then
127                 lsblk $DEV_RDSKDIR/$disk -o TYPE 2>/dev/null | \
128                     egrep loop >/dev/null
129                 return $?
130         fi
131 }
132
133 #
134 # Check if the given device is a multipath device and if there is a sybolic
135 # link to a device mapper and to a disk
136 # Currently no support for dm devices alone without multipath
137 #
138 function is_mpath_device #disk
139 {
140         typeset disk=$1
141         [[ -z $disk ]] && log_fail "No argument for disk given."
142
143         if is_linux; then
144                 lsblk $DEV_MPATHDIR/$disk -o TYPE 2>/dev/null | \
145                    egrep mpath >/dev/null
146                 if (($? == 0)); then
147                         readlink $DEV_MPATHDIR/$disk > /dev/null 2>&1
148                         return $?
149                 else
150                         return $?
151                 fi
152         fi
153 }
154
155 # Set the slice prefix for disk partitioning depending
156 # on whether the device is a real, multipath, or loop device.
157 # Currently all disks have to be of the same type, so only
158 # checks first disk to determine slice prefix.
159 #
160 function set_slice_prefix
161 {
162         typeset disk
163         typeset -i i=0
164
165         if is_linux; then
166                 while (( i < $DISK_ARRAY_NUM )); do
167                         disk="$(echo $DISKS | nawk '{print $(i + 1)}')"
168                         if ( is_mpath_device $disk ) && [[ -z $(echo $disk | awk 'substr($1,18,1)\
169                              ~ /^[[:digit:]]+$/') ]] || ( is_real_device $disk ); then
170                                 export SLICE_PREFIX=""
171                                 return 0
172                         elif ( is_mpath_device $disk || is_loop_device \
173                             $disk ); then
174                                 export SLICE_PREFIX="p"
175                                 return 0
176                         else
177                                 log_fail "$disk not supported for partitioning."
178                         fi
179                         (( i = i + 1))
180                 done
181         fi
182 }
183
184 #
185 # Set the directory path of the listed devices in $DISK_ARRAY_NUM
186 # Currently all disks have to be of the same type, so only
187 # checks first disk to determine device directory
188 # default = /dev (linux)
189 # real disk = /dev (linux)
190 # multipath device = /dev/mapper (linux)
191 #
192 function set_device_dir
193 {
194         typeset disk
195         typeset -i i=0
196
197         if is_linux; then
198                 while (( i < $DISK_ARRAY_NUM )); do
199                         disk="$(echo $DISKS | nawk '{print $(i + 1)}')"
200                         if is_mpath_device $disk; then
201                                 export DEV_DSKDIR=$DEV_MPATHDIR
202                                 return 0
203                         else
204                                 export DEV_DSKDIR=$DEV_RDSKDIR
205                                 return 0
206                         fi
207                         (( i = i + 1))
208                 done
209         else
210                 export DEV_DSKDIR=$DEV_RDSKDIR
211         fi
212 }
213
214 #
215 # Get the directory path of given device
216 #
217 function get_device_dir #device
218 {
219         typeset device=$1
220
221         if ! $(is_physical_device $device) ; then
222                 if [[ $device != "/" ]]; then
223                         device=${device%/*}
224                 fi
225                 if [[ -b "$DEV_DSKDIR/$device" ]]; then
226                         device="$DEV_DSKDIR"
227                 fi
228                 echo $device
229         else
230                 echo "$DEV_DSKDIR"
231         fi
232 }
233
234 #
235 # Get persistent name for given disk
236 #
237 function get_persistent_disk_name #device
238 {
239         typeset device=$1
240         typeset dev_id
241
242         if is_linux; then
243                 if is_real_device $device; then
244                         dev_id="$(udevadm info -q all -n $DEV_DSKDIR/$device \
245                             | egrep disk/by-id | nawk '{print $2; exit}' \
246                             | nawk -F / '{print $3}')"
247                         echo $dev_id
248                 elif is_mpath_device $device; then
249                         dev_id="$(udevadm info -q all -n $DEV_DSKDIR/$device \
250                             | egrep disk/by-id/dm-uuid \
251                             | nawk '{print $2; exit}' \
252                             | nawk -F / '{print $3}')"
253                         echo $dev_id
254                 else
255                         echo $device
256                 fi
257         else
258                 echo $device
259         fi
260 }
261
262 #
263 # Online or offline a disk on the system
264 #
265 # First checks state of disk. Test will fail if disk is not properly onlined
266 # or offlined. Online is a full rescan of SCSI disks by echoing to every
267 # host entry.
268 #
269 function on_off_disk # disk state{online,offline} host
270 {
271         typeset disk=$1
272         typeset state=$2
273         typeset host=$3
274
275         [[ -z $disk ]] || [[ -z $state ]] &&  \
276             log_fail "Arguments invalid or missing"
277
278         if is_linux; then
279                 if [[ $state == "offline" ]] && ( is_mpath_device $disk ); then
280                         dm_name="$(readlink $DEV_DSKDIR/$disk \
281                             | nawk -F / '{print $2}')"
282                         slave="$(ls /sys/block/${dm_name}/slaves \
283                             | nawk '{print $1}')"
284                         while [[ -n $slave ]]; do
285                                 #check if disk is online
286                                 lsscsi | egrep $slave > /dev/null
287                                 if (($? == 0)); then
288                                         slave_dir="/sys/block/${dm_name}"
289                                         slave_dir+="/slaves/${slave}/device"
290                                         ss="${slave_dir}/state"
291                                         sd="${slave_dir}/delete"
292                                         log_must eval "echo 'offline' > ${ss}"
293                                         log_must eval "echo '1' > ${sd}"
294                                         lsscsi | egrep $slave > /dev/null
295                                                 if (($? == 0)); then
296                                                         log_fail "Offlining" \
297                                                             "$disk failed"
298                                                 fi
299                                 fi
300                                 slave="$(ls /sys/block/$dm_name/slaves \
301                                     2>/dev/null | nawk '{print $1}')"
302                         done
303                 elif [[ $state == "offline" ]] && ( is_real_device $disk ); then
304                         #check if disk is online
305                         lsscsi | egrep $disk > /dev/null
306                         if (($? == 0)); then
307                                 dev_state="/sys/block/$disk/device/state"
308                                 dev_delete="/sys/block/$disk/device/delete"
309                                 log_must eval "echo 'offline' > ${dev_state}"
310                                 log_must eval "echo '1' > ${dev_delete}"
311                                 lsscsi | egrep $disk > /dev/null
312                                         if (($? == 0)); then
313                                                 log_fail "Offlining $disk" \
314                                                     "failed"
315                                         fi
316                         else
317                                 log_note "$disk is already offline"
318                         fi
319                 elif [[ $state == "online" ]]; then
320                         #force a full rescan
321                         scan_scsi_hosts $host
322                         block_device_wait
323                         if is_mpath_device $disk; then
324                                 dm_name="$(readlink $DEV_DSKDIR/$disk \
325                                     | nawk -F / '{print $2}')"
326                                 slave="$(ls /sys/block/$dm_name/slaves \
327                                     | nawk '{print $1}')"
328                                 lsscsi | egrep $slave > /dev/null
329                                 if (($? != 0)); then
330                                         log_fail "Onlining $disk failed"
331                                 fi
332                         elif is_real_device $disk; then
333                                 block_device_wait
334                                 typeset -i retries=0
335                                 while ! lsscsi | egrep -q $disk; do
336                                         if (( $retries > 2 )); then
337                                                 log_fail "Onlining $disk failed"
338                                                 break
339                                         fi
340                                         (( ++retries ))
341                                         sleep 1
342                                 done
343                         else
344                                 log_fail "$disk is not a real dev"
345                         fi
346                 else
347                         log_fail "$disk failed to $state"
348                 fi
349         fi
350 }
351
352 #
353 # Simulate disk removal
354 #
355 function remove_disk #disk
356 {
357         typeset disk=$1
358         on_off_disk $disk "offline"
359         block_device_wait
360 }
361
362 #
363 # Simulate disk insertion for the given SCSI host
364 #
365 function insert_disk #disk scsi_host
366 {
367         typeset disk=$1
368         typeset scsi_host=$2
369         on_off_disk $disk "online" $scsi_host
370         block_device_wait
371 }
372
373 #
374 # Load scsi_debug module with specified parameters
375 # $blksz can be either one of: < 512b | 512e | 4Kn >
376 #
377 function load_scsi_debug # dev_size_mb add_host num_tgts max_luns blksz
378 {
379         typeset devsize=$1
380         typeset hosts=$2
381         typeset tgts=$3
382         typeset luns=$4
383         typeset blksz=$5
384
385         [[ -z $devsize ]] || [[ -z $hosts ]] || [[ -z $tgts ]] || \
386             [[ -z $luns ]] || [[ -z $blksz ]] && \
387             log_fail "Arguments invalid or missing"
388
389         case "$5" in
390                 '512b')
391                         typeset sector=512
392                         typeset blkexp=0
393                 ;;
394                 '512e')
395                         typeset sector=512
396                         typeset blkexp=3
397                 ;;
398                 '4Kn')
399                         typeset sector=4096
400                         typeset blkexp=0
401                 ;;
402                 *) log_fail "Unsupported blksz value: $5" ;;
403         esac
404
405         if is_linux; then
406                 modprobe -n scsi_debug
407                 if (($? != 0)); then
408                         log_unsupported "Platform does not have scsi_debug"
409                             "module"
410                 fi
411                 lsmod | egrep scsi_debug > /dev/null
412                 if (($? == 0)); then
413                         log_fail "scsi_debug module already installed"
414                 else
415                         log_must modprobe scsi_debug dev_size_mb=$devsize \
416                             add_host=$hosts num_tgts=$tgts max_luns=$luns \
417                             sector_size=$sector physblk_exp=$blkexp
418                         block_device_wait
419                         lsscsi | egrep scsi_debug > /dev/null
420                         if (($? == 1)); then
421                                 log_fail "scsi_debug module install failed"
422                         fi
423                 fi
424         fi
425 }
426
427 #
428 # Unload scsi_debug module, if needed.
429 #
430 function unload_scsi_debug
431 {
432         log_must_retry "in use" 5 modprobe -r scsi_debug
433 }
434
435 #
436 # Get scsi_debug device name.
437 # Returns basename of scsi_debug device (for example "sdb").
438 #
439 function get_debug_device
440 {
441         for i in {1..10} ; do
442                 val=$(lsscsi | nawk '/scsi_debug/ {print $6; exit}' | cut -d / -f3)
443
444                 # lsscsi can take time to settle
445                 if [ "$val" != "-" ] ; then
446                         break
447                 fi
448                 sleep 1
449         done
450         echo "$val"
451 }
452
453 #
454 # Get actual devices used by the pool (i.e. linux sdb1 not sdb).
455 #
456 function get_pool_devices #testpool #devdir
457 {
458         typeset testpool=$1
459         typeset devdir=$2
460         typeset out=""
461
462         if is_linux; then
463                 out=$(zpool status -P $testpool |grep ${devdir} | awk '{print $1}')
464                 out=$(echo $out | sed -e "s|${devdir}/||g" | tr '\n' ' ')
465         fi
466         echo $out
467 }