1 #
   2 # CDDL HEADER START
   3 #
   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.
   7 #
   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.
  12 #
  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]
  18 #
  19 # CDDL HEADER END
  20 #
  21 
  22 #
  23 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24 # Use is subject to license terms.
  25 #
  26 
  27 #
  28 # Copyright (c) 2013, 2016 by Delphix. All rights reserved.
  29 #
  30 
  31 . $STF_SUITE/include/libtest.shlib
  32 . $STF_SUITE/include/math.shlib
  33 . $STF_SUITE/tests/functional/rsend/rsend.cfg
  34 
  35 #
  36 # Set up test model which includes various datasets
  37 #
  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
  63 #
  64 # $1 pool name
  65 #
  66 function setup_test_model
  67 {
  68         typeset pool=$1
  69 
  70         log_must zfs create -p $pool/$FS/fs1/fs2
  71 
  72         log_must zfs snapshot $pool@psnap
  73         log_must zfs clone $pool@psnap $pool/pclone
  74 
  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
  78 
  79                 log_must zfs snapshot $pool/$FS/vol@vsnap
  80                 log_must zfs clone $pool/$FS/vol@vsnap $pool/$FS/vclone
  81         fi
  82 
  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
  86 
  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
  96 
  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
 103 
 104         log_must zfs snapshot -r $pool@final
 105 
 106         return 0
 107 }
 108 
 109 #
 110 # Cleanup the BACKDIR and given pool content and all the sub datasets
 111 #
 112 # $1 pool name
 113 #
 114 function cleanup_pool
 115 {
 116         typeset pool=$1
 117         log_must rm -rf $BACKDIR/*
 118 
 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
 131 
 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
 138 
 139                 log_must zfs mount $pool
 140         fi
 141         if [[ -d $mntpnt ]]; then
 142                 rm -rf $mntpnt/*
 143         fi
 144 
 145         return 0
 146 }
 147 
 148 function cleanup_pools
 149 {
 150         cleanup_pool $POOL2
 151         destroy_pool $POOL3
 152 }
 153 
 154 #
 155 # Detect if the given two filesystems have same sub-datasets
 156 #
 157 # $1 source filesystem
 158 # $2 destination filesystem
 159 #
 160 function cmp_ds_subs
 161 {
 162         typeset src_fs=$1
 163         typeset dst_fs=$2
 164 
 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
 167 
 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
 170 
 171         diff $BACKDIR/src $BACKDIR/dst
 172         typeset -i ret=$?
 173 
 174         rm -f $BACKDIR/src $BACKDIR/dst $BACKDIR/src1 $BACKDIR/dst1
 175 
 176         return $ret
 177 }
 178 
 179 #
 180 # Compare all the directores and files in two filesystems
 181 #
 182 # $1 source filesystem
 183 # $2 destination filesystem
 184 #
 185 function cmp_ds_cont
 186 {
 187         typeset src_fs=$1
 188         typeset dst_fs=$2
 189 
 190         typeset srcdir dstdir
 191         srcdir=$(get_prop mountpoint $src_fs)
 192         dstdir=$(get_prop mountpoint $dst_fs)
 193 
 194         diff -r $srcdir $dstdir > /dev/null 2>&1
 195         return $?
 196 }
 197 
 198 #
 199 # Compare the given two dataset properties
 200 #
 201 # $1 dataset 1
 202 # $2 dataset 2
 203 #
 204 function cmp_ds_prop
 205 {
 206         typeset dtst1=$1
 207         typeset dtst2=$2
 208 
 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
 220 
 221         eval sed -e 's:$dtst1:PREFIX:g' < $BACKDIR/dtst1 > $BACKDIR/dtst1
 222         eval sed -e 's:$dtst2:PREFIX:g' < $BACKDIR/dtst2 > $BACKDIR/dtst2
 223 
 224         diff $BACKDIR/dtst1 $BACKDIR/dtst2
 225         typeset -i ret=$?
 226 
 227         rm -f $BACKDIR/dtst1 $BACKDIR/dtst2
 228 
 229         return $ret
 230 
 231 }
 232 
 233 #
 234 # Random create directories and files
 235 #
 236 # $1 directory
 237 #
 238 function random_tree
 239 {
 240         typeset dir=$1
 241 
 242         if [[ -d $dir ]]; then
 243                 rm -rf $dir
 244         fi
 245         mkdir -p $dir
 246         typeset -i ret=$?
 247 
 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 |= $?))
 254 
 255         return $ret
 256 }
 257 
 258 #
 259 # Put data in filesystem and take snapshot
 260 #
 261 # $1 snapshot name
 262 #
 263 function snapshot_tree
 264 {
 265         typeset snap=$1
 266         typeset ds=${snap%%@*}
 267         typeset type=$(get_prop "type" $ds)
 268 
 269         typeset -i ret=0
 270         if [[ $type == "filesystem" ]]; then
 271                 typeset mntpnt=$(get_prop mountpoint $ds)
 272                 ((ret |= $?))
 273 
 274                 if ((ret == 0)) ; then
 275                         eval random_tree $mntpnt/${snap##$ds}
 276                         ((ret |= $?))
 277                 fi
 278         fi
 279 
 280         if ((ret == 0)) ; then
 281                 zfs snapshot $snap
 282                 ((ret |= $?))
 283         fi
 284 
 285         return $ret
 286 }
 287 
 288 #
 289 # Destroy the given snapshot and stuff
 290 #
 291 # $1 snapshot
 292 #
 293 function destroy_tree
 294 {
 295         typeset -i ret=0
 296         typeset snap
 297         for snap in "$@" ; do
 298                 zfs destroy $snap
 299                 ret=$?
 300 
 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 |= $?))
 306 
 307                         if ((ret != 0)); then
 308                                 rm -r $mntpnt/$snap
 309                                 ((ret |= $?))
 310                         fi
 311                 fi
 312 
 313                 if ((ret != 0)); then
 314                         return $ret
 315                 fi
 316         done
 317 
 318         return 0
 319 }
 320 
 321 #
 322 # Get all the sub-datasets of give dataset with specific suffix
 323 #
 324 # $1 Given dataset
 325 # $2 Suffix
 326 #
 327 function getds_with_suffix
 328 {
 329         typeset ds=$1
 330         typeset suffix=$2
 331 
 332         typeset list=$(zfs list -r -H -t all -o name $ds | grep "$suffix$")
 333 
 334         echo $list
 335 }
 336 
 337 #
 338 # Output inherited properties whitch is edited for file system
 339 #
 340 function fs_inherit_prop
 341 {
 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
 354 
 355         echo $fs_prop
 356 }
 357 
 358 #
 359 # Output inherited properties for volume
 360 #
 361 function vol_inherit_prop
 362 {
 363         echo "checksum readonly"
 364 }
 365 
 366 #
 367 # Get the destination dataset to compare
 368 #
 369 function get_dst_ds
 370 {
 371         typeset srcfs=$1
 372         typeset dstfs=$2
 373 
 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
 380 
 381         echo $dstfs
 382 }
 383 
 384 #
 385 # Make test files
 386 #
 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
 391 #
 392 function mk_files
 393 {
 394         nfiles=$1
 395         maxsize=$2
 396         file_id_offset=$3
 397         fs=$4
 398 
 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
 407 }
 408 
 409 #
 410 # Remove test files
 411 #
 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
 416 #
 417 function rm_files
 418 {
 419         nfiles=$1
 420         maxsize=$2
 421         file_id_offset=$3
 422         fs=$4
 423 
 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
 428 }
 429 
 430 #
 431 # Mess up file contents
 432 #
 433 # $1 The file path
 434 #
 435 function mess_file
 436 {
 437         file=$1
 438 
 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
 451 }
 452 
 453 #
 454 # Diff the send/receive filesystems
 455 #
 456 # $1 The sent filesystem
 457 # $2 The received filesystem
 458 #
 459 function file_check
 460 {
 461         sendfs=$1
 462         recvfs=$2
 463 
 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
 474 }
 475 
 476 #
 477 # Resume test helper
 478 #
 479 # $1 The ZFS send command
 480 # $2 The filesystem where the streams are sent
 481 # $3 The receive filesystem
 482 #
 483 function resume_test
 484 {
 485         sendcmd=$1
 486         streamfs=$2
 487         recvfs=$3
 488 
 489         stream_num=1
 490         log_must eval "$sendcmd >/$streamfs/$stream_num"
 491 
 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))
 496 
 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
 503 }
 504 
 505 #
 506 # Setup filesystems for the resumable send/receive tests
 507 #
 508 # $1 The "send" filesystem
 509 # $2 The "recv" filesystem
 510 #
 511 function test_fs_setup
 512 {
 513         typeset sendfs=$1
 514         typeset recvfs=$2
 515         typeset streamfs=$3
 516         typeset sendpool=${sendfs%%/*}
 517         typeset recvpool=${recvfs%%/*}
 518 
 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
 522 
 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
 531 
 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
 537 
 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
 543 
 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
 549 
 550         log_must zfs create -o compress=lz4 $streamfs
 551 }
 552 
 553 #
 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
 556 #
 557 # $1 The stream file
 558 # $2-$n The flags expected in the stream
 559 #
 560 function stream_has_features
 561 {
 562         typeset file=$1
 563         shift
 564 
 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"
 578 
 579         typeset flag known derived=0
 580         for flag in "$@"; do
 581                 known=${feature[$flag]}
 582                 [[ -z $known ]] && log_fail "Unknown feature: $flag"
 583 
 584                 derived=$(echo "$flags & ${feature[$flag]} = X" | mdb | sed 's/ //g')
 585                 [[ $derived = $known ]] || return 1
 586         done
 587 
 588         return 0
 589 }
 590 
 591 #
 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>"
 601 #
 602 function parse_dump
 603 {
 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}'
 610 }
 611 
 612 #
 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.
 618 #
 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
 624 #
 625 
 626 function verify_stream_size
 627 {
 628         typeset stream=$1
 629         typeset ds=$2
 630         typeset percent=${3:-90}
 631         typeset inc_src=$4
 632 
 633         [[ -f $stream ]] || log_fail "No such file: $stream"
 634         datasetexists $ds || log_fail "No such dataset: $ds"
 635 
 636         typeset stream_size=$(cat $stream | zstreamdump | sed -n \
 637             's/ Total write size = \(.*\) (0x.*)/\1/p')
 638 
 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
 646 
 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))
 653 
 654         within_percent $stream_size $ds_size $percent || log_fail \
 655             "$stream_size $ds_size differed by too much"
 656 }
 657 
 658 # Cleanup function for tests involving resumable send
 659 function resume_cleanup
 660 {
 661         typeset sendfs=$1
 662         typeset streamfs=$2
 663         typeset sendpool=${sendfs%%/*}
 664 
 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
 669 }