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# 66function 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# 114function 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 148function 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# 160function 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# 185function 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# 204function 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 "dnodesize" "exec" "quota" "readonly" "recordsize" "reservation" \ 212 "setuid" "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# 238function 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# 263function 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# 293function 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# 327function 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# 340function 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# 361function vol_inherit_prop 362{ 363 echo "checksum readonly" 364} 365 366# 367# Get the destination dataset to compare 368# 369function 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# 392function 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# 417function 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# Simulate a random set of operations which could be reasonably expected 432# to occur on an average filesystem. 433# 434# $1 Number of files to modify 435# $2 Maximum file size 436# $3 File system to modify the file on 437# $4 Enabled xattrs (optional) 438# 439function churn_files 440{ 441 nfiles=$1 442 maxsize=$2 443 fs=$3 444 xattrs=${4:-1} 445 446 # 447 # Remove roughly half of the files in order to make it more 448 # likely that a dnode will be reallocated. 449 # 450 for ((i=0; i<$nfiles; i=i+1)); do 451 file_name="/$fs/file-$i" 452 453 if [[ -e $file_name ]]; then 454 if [ $((RANDOM % 2)) -eq 0 ]; then 455 rm $file_name || \ 456 log_fail "Failed to remove $file_name" 457 fi 458 fi 459 done 460 461 # 462 # Remount the filesystem to simulate normal usage. This resets 463 # the last allocated object id allowing for new objects to be 464 # reallocated in the locations of previously freed objects. 465 # 466 log_must zfs unmount $fs 467 log_must zfs mount $fs 468 469 for i in {0..$nfiles}; do 470 file_name="/$fs/file-$i" 471 file_size=$((($RANDOM * $RANDOM % ($maxsize - 1)) + 1)) 472 473 # 474 # When the file exists modify it in one of five ways to 475 # simulate normal usage: 476 # - (20%) Remove and set and extended attribute on the file 477 # - (20%) Overwrite the existing file 478 # - (20%) Truncate the existing file to a random length 479 # - (20%) Truncate the existing file to zero length 480 # - (20%) Remove the file 481 # 482 # Otherwise create the missing file. 20% of the created 483 # files will be small and use embedded block pointers, the 484 # remainder with have random sizes up to the maximum size. 485 # Three extended attributes are attached to all of the files. 486 # 487 if [[ -e $file_name ]]; then 488 value=$((RANDOM % 5)) 489 if [ $value -eq 0 -a $xattrs -ne 0 ]; then 490 attrname="testattr$((RANDOM % 3))" 491 attr -qr $attrname $file_name || \ 492 log_fail "Failed to remove $attrname" 493 attr -qs $attrname -V TestValue $file_name || \ 494 log_fail "Failed to set $attrname" 495 elif [ $value -eq 1 ]; then 496 dd if=/dev/urandom of=$file_name \ 497 bs=$file_size count=1 >/dev/null 2>&1 || \ 498 log_fail "Failed to overwrite $file_name" 499 elif [ $value -eq 2 ]; then 500 truncate -s $file_size $file_name || \ 501 log_fail "Failed to truncate $file_name" 502 elif [ $value -eq 3 ]; then 503 truncate -s 0 $file_name || \ 504 log_fail "Failed to truncate $file_name" 505 else 506 rm $file_name || \ 507 log_fail "Failed to remove $file_name" 508 fi 509 else 510 if [ $((RANDOM % 5)) -eq 0 ]; then 511 file_size=$((($RANDOM % 64) + 1)) 512 fi 513 514 dd if=/dev/urandom of=$file_name \ 515 bs=$file_size count=1 >/dev/null 2>&1 || \ 516 log_fail "Failed to create $file_name" 517 518 if [ $xattrs -ne 0 ]; then 519 for j in {0..2}; do 520 attrname="testattr$j" 521 attr -qs $attrname -V TestValue \ 522 $file_name || log_fail \ 523 "Failed to set $attrname" 524 done 525 fi 526 fi 527 done 528 529 return 0 530} 531 532# 533# Mess up file contents 534# 535# $1 The file path 536# 537function mess_file 538{ 539 file=$1 540 541 filesize=$(stat -c '%s' $file) 542 offset=$(($RANDOM * $RANDOM % $filesize)) 543 if (($RANDOM % 7 <= 1)); then 544 # 545 # We corrupt 2 bytes to minimize the chance that we 546 # write the same value that's already there. 547 # 548 log_must eval "dd if=/dev/random of=$file conv=notrunc " \ 549 "bs=1 count=2 oseek=$offset >/dev/null 2>&1" 550 else 551 log_must truncate -s $offset $file 552 fi 553} 554 555# 556# Diff the send/receive filesystems 557# 558# $1 The sent filesystem 559# $2 The received filesystem 560# 561function file_check 562{ 563 sendfs=$1 564 recvfs=$2 565 566 if [[ -d /$recvfs/.zfs/snapshot/a && -d \ 567 /$sendfs/.zfs/snapshot/a ]]; then 568 diff -r /$recvfs/.zfs/snapshot/a /$sendfs/.zfs/snapshot/a 569 [[ $? -eq 0 ]] || log_fail "Differences found in snap a" 570 fi 571 if [[ -d /$recvfs/.zfs/snapshot/b && -d \ 572 /$sendfs/.zfs/snapshot/b ]]; then 573 diff -r /$recvfs/.zfs/snapshot/b /$sendfs/.zfs/snapshot/b 574 [[ $? -eq 0 ]] || log_fail "Differences found in snap b" 575 fi 576} 577 578# 579# Resume test helper 580# 581# $1 The ZFS send command 582# $2 The filesystem where the streams are sent 583# $3 The receive filesystem 584# 585function resume_test 586{ 587 sendcmd=$1 588 streamfs=$2 589 recvfs=$3 590 591 stream_num=1 592 log_must eval "$sendcmd >/$streamfs/$stream_num" 593 594 for ((i=0; i<2; i=i+1)); do 595 mess_file /$streamfs/$stream_num 596 log_mustnot zfs recv -suv $recvfs </$streamfs/$stream_num 597 stream_num=$((stream_num+1)) 598 599 token=$(zfs get -Hp -o value receive_resume_token $recvfs) 600 log_must eval "zfs send -v -t $token >/$streamfs/$stream_num" 601 [[ -f /$streamfs/$stream_num ]] || \ 602 log_fail "NO FILE /$streamfs/$stream_num" 603 done 604 log_must zfs recv -suv $recvfs </$streamfs/$stream_num 605} 606 607# 608# Setup filesystems for the resumable send/receive tests 609# 610# $1 The "send" filesystem 611# $2 The "recv" filesystem 612# 613function test_fs_setup 614{ 615 typeset sendfs=$1 616 typeset recvfs=$2 617 typeset streamfs=$3 618 typeset sendpool=${sendfs%%/*} 619 typeset recvpool=${recvfs%%/*} 620 621 datasetexists $sendfs && log_must zfs destroy -r $sendpool 622 datasetexists $recvfs && log_must zfs destroy -r $recvpool 623 datasetexists $streamfs && log_must zfs destroy -r $streamfs 624 625 if $(datasetexists $sendfs || zfs create -o compress=lz4 $sendfs); then 626 mk_files 1000 256 0 $sendfs & 627 mk_files 1000 131072 0 $sendfs & 628 mk_files 100 1048576 0 $sendfs & 629 mk_files 10 10485760 0 $sendfs & 630 mk_files 1 104857600 0 $sendfs & 631 log_must wait 632 log_must zfs snapshot $sendfs@a 633 634 rm_files 200 256 0 $sendfs & 635 rm_files 200 131072 0 $sendfs & 636 rm_files 20 1048576 0 $sendfs & 637 rm_files 2 10485760 0 $sendfs & 638 log_must wait 639 640 mk_files 400 256 0 $sendfs & 641 mk_files 400 131072 0 $sendfs & 642 mk_files 40 1048576 0 $sendfs & 643 mk_files 4 10485760 0 $sendfs & 644 log_must wait 645 646 log_must zfs snapshot $sendfs@b 647 log_must eval "zfs send -v $sendfs@a >/$sendpool/initial.zsend" 648 log_must eval "zfs send -v -i @a $sendfs@b " \ 649 ">/$sendpool/incremental.zsend" 650 fi 651 652 log_must zfs create -o compress=lz4 $streamfs 653} 654 655# 656# Check to see if the specified features are set in a send stream. 657# The values for these features are found in uts/common/fs/zfs/sys/zfs_ioctl.h 658# 659# $1 The stream file 660# $2-$n The flags expected in the stream 661# 662function stream_has_features 663{ 664 typeset file=$1 665 shift 666 667 [[ -f $file ]] || log_fail "Couldn't find file: $file" 668 typeset flags=$(cat $file | zstreamdump | awk '/features =/ {print $3}') 669 typeset -A feature 670 feature[dedup]="1" 671 feature[dedupprops]="2" 672 feature[sa_spill]="4" 673 feature[embed_data]="10000" 674 feature[lz4]="20000" 675 feature[mooch_byteswap]="40000" 676 feature[large_blocks]="80000" 677 feature[resuming]="100000" 678 feature[redacted]="200000" 679 feature[compressed]="400000" 680 681 typeset flag known derived=0 682 for flag in "$@"; do 683 known=${feature[$flag]} 684 [[ -z $known ]] && log_fail "Unknown feature: $flag" 685 686 derived=$(echo "$flags & ${feature[$flag]} = X" | mdb | sed 's/ //g') 687 [[ $derived = $known ]] || return 1 688 done 689 690 return 0 691} 692 693# 694# Parse zstreamdump -v output. The output varies for each kind of record: 695# BEGIN records are simply output as "BEGIN" 696# END records are output as "END" 697# OBJECT records become "OBJECT <object num>" 698# FREEOBJECTS records become "FREEOBJECTS <startobj> <numobjs>" 699# FREE records become "<record type> <start> <length>" 700# WRITE records become: 701# "<record type> <compression type> <start> <logical size> <compressed size> 702# <data size>" 703# 704function parse_dump 705{ 706 sed '/^WRITE/{N;s/\n/ /;}' | grep "^[A-Z]" | awk '{ 707 if ($1 == "BEGIN" || $1 == "END") print $1 708 if ($1 == "OBJECT") print $1" "$4 709 if ($1 == "FREEOBJECTS") print $1" "$4" "$7 710 if ($1 == "FREE") print $1" "$7" "$10 711 if ($1 == "WRITE") print $1" "$15" "$21" "$24" "$27" "$30}' 712} 713 714# 715# Given a send stream, verify that the size of the stream matches what's 716# expected based on the source or target dataset. If the stream is an 717# incremental stream, subtract the size of the source snapshot before 718# comparing. This function does not currently handle incremental streams 719# that remove data. 720# 721# $1 The zstreamdump output file 722# $2 The dataset to compare against 723# This can be a source of a send or recv target (fs, not snapshot) 724# $3 The percentage below which verification is deemed a failure 725# $4 The source snapshot of an incremental send 726# 727 728function verify_stream_size 729{ 730 typeset stream=$1 731 typeset ds=$2 732 typeset percent=${3:-90} 733 typeset inc_src=$4 734 735 [[ -f $stream ]] || log_fail "No such file: $stream" 736 datasetexists $ds || log_fail "No such dataset: $ds" 737 738 typeset stream_size=$(cat $stream | zstreamdump | sed -n \ 739 's/ Total write size = \(.*\) (0x.*)/\1/p') 740 741 typeset inc_size=0 742 if [[ -n $inc_src ]]; then 743 inc_size=$(get_prop lrefer $inc_src) 744 if stream_has_features $stream compressed; then 745 inc_size=$(get_prop refer $inc_src) 746 fi 747 fi 748 749 if stream_has_features $stream compressed; then 750 ds_size=$(get_prop refer $ds) 751 else 752 ds_size=$(get_prop lrefer $ds) 753 fi 754 ds_size=$((ds_size - inc_size)) 755 756 within_percent $stream_size $ds_size $percent || log_fail \ 757 "$stream_size $ds_size differed by too much" 758} 759 760# Cleanup function for tests involving resumable send 761function resume_cleanup 762{ 763 typeset sendfs=$1 764 typeset streamfs=$2 765 typeset sendpool=${sendfs%%/*} 766 767 datasetexists $sendfs && log_must zfs destroy -r $sendfs 768 datasetexists $streamfs && log_must zfs destroy -r $streamfs 769 cleanup_pool $POOL2 770 rm -f /$sendpool/initial.zsend /$sendpool/incremental.zsend 771} 772