8940 Sending an intra-pool resumable send stream may result in EXDEV
[unleashed.git] / usr / src / test / zfs-tests / tests / functional / rsend / rsend.kshlib
blob8f1f30ed267f0b2e2f21cb12a5f1727c957d94e7
2 # CDDL HEADER START
4 # The contents of this file are subject to the terms of the
5 # Common Development and Distribution License (the "License").
6 # You may not use this file except in compliance with the License.
8 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 # or http://www.opensolaris.org/os/licensing.
10 # See the License for the specific language governing permissions
11 # and limitations under the License.
13 # When distributing Covered Code, include this CDDL HEADER in each
14 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 # If applicable, add the following below this CDDL HEADER, with the
16 # fields enclosed by brackets "[]" replaced with your own identifying
17 # information: Portions Copyright [yyyy] [name of copyright owner]
19 # CDDL HEADER END
23 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 # Use is subject to license terms.
28 # Copyright (c) 2013, 2016 by Delphix. All rights reserved.
31 . $STF_SUITE/include/libtest.shlib
32 . $STF_SUITE/include/math.shlib
33 . $STF_SUITE/tests/functional/rsend/rsend.cfg
36 # Set up test model which includes various datasets
38 #               @final
39 #               @snapB
40 #               @init
41 #               |
42 #   ______ pclone
43 #  |      /
44 #  |@psnap
45 #  ||                         @final
46 #  ||@final       @final      @snapC
47 #  ||@snapC       @snapC      @snapB
48 #  ||@snapA       @snapB      @snapA
49 #  ||@init        @init       @init
50 #  |||            |           |
51 # $pool -------- $FS ------- fs1 ------- fs2
52 #    \             \\_____     \          |
53 #     vol           vol   \____ \         @fsnap
54 #      |              |        \ \              \
55 #      @init          @vsnap   |  ------------ fclone
56 #      @snapA         @init \  |                    |
57 #      @final         @snapB \ |                    @init
58 #                     @snapC  vclone                @snapA
59 #                     @final       |                @final
60 #                                 @init
61 #                                 @snapC
62 #                                 @final
64 # $1 pool name
66 function setup_test_model
68         typeset pool=$1
70         log_must zfs create -p $pool/$FS/fs1/fs2
72         log_must zfs snapshot $pool@psnap
73         log_must zfs clone $pool@psnap $pool/pclone
75         if is_global_zone ; then
76                 log_must zfs create -V 16M $pool/vol
77                 log_must zfs create -V 16M $pool/$FS/vol
79                 log_must zfs snapshot $pool/$FS/vol@vsnap
80                 log_must zfs clone $pool/$FS/vol@vsnap $pool/$FS/vclone
81         fi
83         log_must snapshot_tree $pool/$FS/fs1/fs2@fsnap
84         log_must zfs clone $pool/$FS/fs1/fs2@fsnap $pool/$FS/fs1/fclone
85         log_must zfs snapshot -r $pool@init
87         log_must snapshot_tree $pool@snapA
88         log_must snapshot_tree $pool@snapC
89         log_must snapshot_tree $pool/pclone@snapB
90         log_must snapshot_tree $pool/$FS@snapB
91         log_must snapshot_tree $pool/$FS@snapC
92         log_must snapshot_tree $pool/$FS/fs1@snapA
93         log_must snapshot_tree $pool/$FS/fs1@snapB
94         log_must snapshot_tree $pool/$FS/fs1@snapC
95         log_must snapshot_tree $pool/$FS/fs1/fclone@snapA
97         if is_global_zone ; then
98                 log_must zfs snapshot $pool/vol@snapA
99                 log_must zfs snapshot $pool/$FS/vol@snapB
100                 log_must zfs snapshot $pool/$FS/vol@snapC
101                 log_must zfs snapshot $pool/$FS/vclone@snapC
102         fi
104         log_must zfs snapshot -r $pool@final
106         return 0
110 # Cleanup the BACKDIR and given pool content and all the sub datasets
112 # $1 pool name
114 function cleanup_pool
116         typeset pool=$1
117         log_must rm -rf $BACKDIR/*
119         if is_global_zone ; then
120                 log_must zfs destroy -Rf $pool
121         else
122                 typeset list=$(zfs list -H -r -t all -o name $pool)
123                 for ds in $list ; do
124                         if [[ $ds != $pool ]] ; then
125                                 if datasetexists $ds ; then
126                                         log_must zfs destroy -Rf $ds
127                                 fi
128                         fi
129                 done
130         fi
132         typeset mntpnt=$(get_prop mountpoint $pool)
133         if ! ismounted $pool ; then
134                 # Make sure mountpoint directory is empty
135                 if [[ -d $mntpnt ]]; then
136                         log_must rm -rf $mntpnt/*
137                 fi
139                 log_must zfs mount $pool
140         fi
141         if [[ -d $mntpnt ]]; then
142                 rm -rf $mntpnt/*
143         fi
145         return 0
148 function cleanup_pools
150         cleanup_pool $POOL2
151         destroy_pool $POOL3
155 # Detect if the given two filesystems have same sub-datasets
157 # $1 source filesystem
158 # $2 destination filesystem
160 function cmp_ds_subs
162         typeset src_fs=$1
163         typeset dst_fs=$2
165         zfs list -r -H -t all -o name $src_fs > $BACKDIR/src1
166         zfs list -r -H -t all -o name $dst_fs > $BACKDIR/dst1
168         eval sed -e 's:^$src_fs:PREFIX:g' < $BACKDIR/src1 > $BACKDIR/src
169         eval sed -e 's:^$dst_fs:PREFIX:g' < $BACKDIR/dst1 > $BACKDIR/dst
171         diff $BACKDIR/src $BACKDIR/dst
172         typeset -i ret=$?
174         rm -f $BACKDIR/src $BACKDIR/dst $BACKDIR/src1 $BACKDIR/dst1
176         return $ret
180 # Compare all the directores and files in two filesystems
182 # $1 source filesystem
183 # $2 destination filesystem
185 function cmp_ds_cont
187         typeset src_fs=$1
188         typeset dst_fs=$2
190         typeset srcdir dstdir
191         srcdir=$(get_prop mountpoint $src_fs)
192         dstdir=$(get_prop mountpoint $dst_fs)
194         diff -r $srcdir $dstdir > /dev/null 2>&1
195         return $?
199 # Compare the given two dataset properties
201 # $1 dataset 1
202 # $2 dataset 2
204 function cmp_ds_prop
206         typeset dtst1=$1
207         typeset dtst2=$2
209         for item in "type" "origin" "volblocksize" "aclinherit" "aclmode" \
210             "atime" "canmount" "checksum" "compression" "copies" "devices" \
211             "exec" "quota" "readonly" "recordsize" "reservation" "setuid" \
212             "sharenfs" "snapdir" "version" "volsize" "xattr" "zoned" \
213             "mountpoint";
214         do
215                 zfs get -H -o property,value,source $item $dtst1 >> \
216                     $BACKDIR/dtst1
217                 zfs get -H -o property,value,source $item $dtst2 >> \
218                     $BACKDIR/dtst2
219         done
221         eval sed -e 's:$dtst1:PREFIX:g' < $BACKDIR/dtst1 > $BACKDIR/dtst1
222         eval sed -e 's:$dtst2:PREFIX:g' < $BACKDIR/dtst2 > $BACKDIR/dtst2
224         diff $BACKDIR/dtst1 $BACKDIR/dtst2
225         typeset -i ret=$?
227         rm -f $BACKDIR/dtst1 $BACKDIR/dtst2
229         return $ret
234 # Random create directories and files
236 # $1 directory
238 function random_tree
240         typeset dir=$1
242         if [[ -d $dir ]]; then
243                 rm -rf $dir
244         fi
245         mkdir -p $dir
246         typeset -i ret=$?
248         typeset -i nl nd nf
249         ((nl = RANDOM % 6 + 1))
250         ((nd = RANDOM % 3 ))
251         ((nf = RANDOM % 5 ))
252         mktree -b $dir -l $nl -d $nd -f $nf
253         ((ret |= $?))
255         return $ret
259 # Put data in filesystem and take snapshot
261 # $1 snapshot name
263 function snapshot_tree
265         typeset snap=$1
266         typeset ds=${snap%%@*}
267         typeset type=$(get_prop "type" $ds)
269         typeset -i ret=0
270         if [[ $type == "filesystem" ]]; then
271                 typeset mntpnt=$(get_prop mountpoint $ds)
272                 ((ret |= $?))
274                 if ((ret == 0)) ; then
275                         eval random_tree $mntpnt/${snap##$ds}
276                         ((ret |= $?))
277                 fi
278         fi
280         if ((ret == 0)) ; then
281                 zfs snapshot $snap
282                 ((ret |= $?))
283         fi
285         return $ret
289 # Destroy the given snapshot and stuff
291 # $1 snapshot
293 function destroy_tree
295         typeset -i ret=0
296         typeset snap
297         for snap in "$@" ; do
298                 zfs destroy $snap
299                 ret=$?
301                 typeset ds=${snap%%@*}
302                 typeset type=$(get_prop "type" $ds)
303                 if [[ $type == "filesystem" ]]; then
304                         typeset mntpnt=$(get_prop mountpoint $ds)
305                         ((ret |= $?))
307                         if ((ret != 0)); then
308                                 rm -r $mntpnt/$snap
309                                 ((ret |= $?))
310                         fi
311                 fi
313                 if ((ret != 0)); then
314                         return $ret
315                 fi
316         done
318         return 0
322 # Get all the sub-datasets of give dataset with specific suffix
324 # $1 Given dataset
325 # $2 Suffix
327 function getds_with_suffix
329         typeset ds=$1
330         typeset suffix=$2
332         typeset list=$(zfs list -r -H -t all -o name $ds | grep "$suffix$")
334         echo $list
338 # Output inherited properties whitch is edited for file system
340 function fs_inherit_prop
342         typeset fs_prop
343         if is_global_zone ; then
344                 fs_prop=$(zfs inherit 2>&1 | \
345                     awk '$2=="YES" && $3=="YES" {print $1}')
346                 if ! is_te_enabled ; then
347                         fs_prop=$(echo $fs_prop | grep -v "mlslabel")
348                 fi
349         else
350                 fs_prop=$(zfs inherit 2>&1 | \
351                     awk '$2=="YES" && $3=="YES" {print $1}'|
352                     egrep -v "devices|mlslabel|sharenfs|sharesmb|zoned")
353         fi
355         echo $fs_prop
359 # Output inherited properties for volume
361 function vol_inherit_prop
363         echo "checksum readonly"
367 # Get the destination dataset to compare
369 function get_dst_ds
371         typeset srcfs=$1
372         typeset dstfs=$2
374         #
375         # If the srcfs is not pool
376         #
377         if ! zpool list $srcfs > /dev/null 2>&1 ; then
378                 eval dstfs="$dstfs/${srcfs#*/}"
379         fi
381         echo $dstfs
385 # Make test files
387 # $1 Number of files to create
388 # $2 Maximum file size
389 # $3 File ID offset
390 # $4 File system to create the files on
392 function mk_files
394         nfiles=$1
395         maxsize=$2
396         file_id_offset=$3
397         fs=$4
399         for ((i=0; i<$nfiles; i=i+1)); do
400                 dd if=/dev/urandom \
401                     of=/$fs/file-$maxsize-$((i+$file_id_offset)) \
402                     bs=$(($RANDOM * $RANDOM % $maxsize)) \
403                     count=1 >/dev/null 2>&1 || log_fail \
404                     "Failed to create /$fs/file-$maxsize-$((i+$file_id_offset))"
405         done
406         echo Created $nfiles files of random sizes up to $maxsize bytes
410 # Remove test files
412 # $1 Number of files to remove
413 # $2 Maximum file size
414 # $3 File ID offset
415 # $4 File system to remove the files from
417 function rm_files
419         nfiles=$1
420         maxsize=$2
421         file_id_offset=$3
422         fs=$4
424         for ((i=0; i<$nfiles; i=i+1)); do
425                 rm -f /$fs/file-$maxsize-$((i+$file_id_offset))
426         done
427         echo Removed $nfiles files of random sizes up to $maxsize bytes
431 # Mess up file contents
433 # $1 The file path
435 function mess_file
437         file=$1
439         filesize=$(stat -c '%s' $file)
440         offset=$(($RANDOM * $RANDOM % $filesize))
441         if (($RANDOM % 7 <= 1)); then
442                 #
443                 # We corrupt 2 bytes to minimize the chance that we
444                 # write the same value that's already there.
445                 #
446                 log_must eval "dd if=/dev/random of=$file conv=notrunc " \
447                     "bs=1 count=2 oseek=$offset >/dev/null 2>&1"
448         else
449                 log_must truncate -s $offset $file
450         fi
454 # Diff the send/receive filesystems
456 # $1 The sent filesystem
457 # $2 The received filesystem
459 function file_check
461         sendfs=$1
462         recvfs=$2
464         if [[ -d /$recvfs/.zfs/snapshot/a && -d \
465             /$sendfs/.zfs/snapshot/a ]]; then
466                 diff -r /$recvfs/.zfs/snapshot/a /$sendfs/.zfs/snapshot/a
467                 [[ $? -eq 0 ]] || log_fail "Differences found in snap a"
468         fi
469         if [[ -d /$recvfs/.zfs/snapshot/b && -d \
470             /$sendfs/.zfs/snapshot/b ]]; then
471                 diff -r /$recvfs/.zfs/snapshot/b /$sendfs/.zfs/snapshot/b
472                 [[ $? -eq 0 ]] || log_fail "Differences found in snap b"
473         fi
477 # Resume test helper
479 # $1 The ZFS send command
480 # $2 The filesystem where the streams are sent
481 # $3 The receive filesystem
483 function resume_test
485         sendcmd=$1
486         streamfs=$2
487         recvfs=$3
489         stream_num=1
490         log_must eval "$sendcmd >/$streamfs/$stream_num"
492         for ((i=0; i<2; i=i+1)); do
493                 mess_file /$streamfs/$stream_num
494                 log_mustnot zfs recv -suv $recvfs </$streamfs/$stream_num
495                 stream_num=$((stream_num+1))
497                 token=$(zfs get -Hp -o value receive_resume_token $recvfs)
498                 log_must eval "zfs send -v -t $token >/$streamfs/$stream_num"
499                 [[ -f /$streamfs/$stream_num ]] || \
500                     log_fail "NO FILE /$streamfs/$stream_num"
501         done
502         log_must zfs recv -suv $recvfs </$streamfs/$stream_num
506 # Setup filesystems for the resumable send/receive tests
508 # $1 The "send" filesystem
509 # $2 The "recv" filesystem
511 function test_fs_setup
513         typeset sendfs=$1
514         typeset recvfs=$2
515         typeset streamfs=$3
516         typeset sendpool=${sendfs%%/*}
517         typeset recvpool=${recvfs%%/*}
519         datasetexists $sendfs && log_must zfs destroy -r $sendpool
520         datasetexists $recvfs && log_must zfs destroy -r $recvpool
521         datasetexists $streamfs && log_must zfs destroy -r $streamfs
523         if $(datasetexists $sendfs || zfs create -o compress=lz4 $sendfs); then
524                 mk_files 1000 256 0 $sendfs &
525                 mk_files 1000 131072 0 $sendfs &
526                 mk_files 100 1048576 0 $sendfs &
527                 mk_files 10 10485760 0 $sendfs &
528                 mk_files 1 104857600 0 $sendfs &
529                 log_must wait
530                 log_must zfs snapshot $sendfs@a
532                 rm_files 200 256 0 $sendfs &
533                 rm_files 200 131072 0 $sendfs &
534                 rm_files 20 1048576 0 $sendfs &
535                 rm_files 2 10485760 0 $sendfs &
536                 log_must wait
538                 mk_files 400 256 0 $sendfs &
539                 mk_files 400 131072 0 $sendfs &
540                 mk_files 40 1048576 0 $sendfs &
541                 mk_files 4 10485760 0 $sendfs &
542                 log_must wait
544                 log_must zfs snapshot $sendfs@b
545                 log_must eval "zfs send -v $sendfs@a >/$sendpool/initial.zsend"
546                 log_must eval "zfs send -v -i @a $sendfs@b " \
547                     ">/$sendpool/incremental.zsend"
548         fi
550         log_must zfs create -o compress=lz4 $streamfs
554 # Check to see if the specified features are set in a send stream.
555 # The values for these features are found in uts/common/fs/zfs/sys/zfs_ioctl.h
557 # $1 The stream file
558 # $2-$n The flags expected in the stream
560 function stream_has_features
562         typeset file=$1
563         shift
565         [[ -f $file ]] || log_fail "Couldn't find file: $file"
566         typeset flags=$(cat $file | zstreamdump | awk '/features =/ {print $3}')
567         typeset -A feature
568         feature[dedup]="1"
569         feature[dedupprops]="2"
570         feature[sa_spill]="4"
571         feature[embed_data]="10000"
572         feature[lz4]="20000"
573         feature[mooch_byteswap]="40000"
574         feature[large_blocks]="80000"
575         feature[resuming]="100000"
576         feature[redacted]="200000"
577         feature[compressed]="400000"
579         typeset flag known derived=0
580         for flag in "$@"; do
581                 known=${feature[$flag]}
582                 [[ -z $known ]] && log_fail "Unknown feature: $flag"
584                 derived=$(echo "$flags & ${feature[$flag]} = X" | mdb | sed 's/ //g')
585                 [[ $derived = $known ]] || return 1
586         done
588         return 0
592 # Parse zstreamdump -v output.  The output varies for each kind of record:
593 # BEGIN records are simply output as "BEGIN"
594 # END records are output as "END"
595 # OBJECT records become "OBJECT <object num>"
596 # FREEOBJECTS records become "FREEOBJECTS <startobj> <numobjs>"
597 # FREE records become "<record type> <start> <length>"
598 # WRITE records become:
599 # "<record type> <compression type> <start> <logical size> <compressed size>
600 #  <data size>"
602 function parse_dump
604         sed '/^WRITE/{N;s/\n/ /;}' | grep "^[A-Z]" | awk '{
605             if ($1 == "BEGIN" || $1 == "END") print $1
606             if ($1 == "OBJECT") print $1" "$4
607             if ($1 == "FREEOBJECTS") print $1" "$4" "$7
608             if ($1 == "FREE") print $1" "$7" "$10
609             if ($1 == "WRITE") print $1" "$15" "$18" "$21" "$24" "$27}'
613 # Given a send stream, verify that the size of the stream matches what's
614 # expected based on the source or target dataset. If the stream is an
615 # incremental stream, subtract the size of the source snapshot before
616 # comparing. This function does not currently handle incremental streams
617 # that remove data.
619 # $1 The zstreamdump output file
620 # $2 The dataset to compare against
621 #    This can be a source of a send or recv target (fs, not snapshot)
622 # $3 The percentage below which verification is deemed a failure
623 # $4 The source snapshot of an incremental send
626 function verify_stream_size
628         typeset stream=$1
629         typeset ds=$2
630         typeset percent=${3:-90}
631         typeset inc_src=$4
633         [[ -f $stream ]] || log_fail "No such file: $stream"
634         datasetexists $ds || log_fail "No such dataset: $ds"
636         typeset stream_size=$(cat $stream | zstreamdump | sed -n \
637             's/ Total write size = \(.*\) (0x.*)/\1/p')
639         typeset inc_size=0
640         if [[ -n $inc_src ]]; then
641                 inc_size=$(get_prop lrefer $inc_src)
642                 if stream_has_features $stream compressed; then
643                         inc_size=$(get_prop refer $inc_src)
644                 fi
645         fi
647         if stream_has_features $stream compressed; then
648                 ds_size=$(get_prop refer $ds)
649         else
650                 ds_size=$(get_prop lrefer $ds)
651         fi
652         ds_size=$((ds_size - inc_size))
654         within_percent $stream_size $ds_size $percent || log_fail \
655             "$stream_size $ds_size differed by too much"
658 # Cleanup function for tests involving resumable send
659 function resume_cleanup
661         typeset sendfs=$1
662         typeset streamfs=$2
663         typeset sendpool=${sendfs%%/*}
665         datasetexists $sendfs && log_must zfs destroy -r $sendfs
666         datasetexists $streamfs && log_must zfs destroy -r $streamfs
667         cleanup_pool $POOL2
668         rm -f /$sendpool/initial.zsend /$sendpool/incremental.zsend