mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 02:27:36 +03:00
OpenZFS 8677 - Open-Context Channel Programs
Authored by: Serapheim Dimitropoulos <serapheim@delphix.com> Reviewed by: Matt Ahrens <mahrens@delphix.com> Reviewed by: Chris Williamson <chris.williamson@delphix.com> Reviewed by: Pavel Zakharov <pavel.zakharov@delphix.com> Approved by: Robert Mustacchi <rm@joyent.com> Ported-by: Don Brady <don.brady@delphix.com> We want to be able to run channel programs outside of synching context. This would greatly improve performance for channel programs that just gather information, as they won't have to wait for synching context anymore. === What is implemented? This feature introduces the following: - A new command line flag in "zfs program" to specify our intention to run in open context. (The -n option) - A new flag/option within the channel program ioctl which selects the context. - Appropriate error handling whenever we try a channel program in open-context that contains zfs.sync* expressions. - Documentation for the new feature in the manual pages. === How do we handle zfs.sync functions in open context? When such a function is found by the interpreter and we are running in open context we abort the script and we spit out a descriptive runtime error. For example, given the script below ... arg = ... fs = arg["argv"][1] err = zfs.sync.destroy(fs) msg = "destroying " .. fs .. " err=" .. err return msg if we run it in open context, we will get back the following error: Channel program execution failed: [string "channel program"]:3: running functions from the zfs.sync submodule requires passing sync=TRUE to lzc_channel_program() (i.e. do not specify the "-n" command line argument) stack traceback: [C]: in function 'destroy' [string "channel program"]:3: in main chunk === What about testing? We've introduced new wrappers for all channel program tests that run each channel program as both (startard & open-context) and expect the appropriate behavior depending on the program using the zfs.sync module. OpenZFS-issue: https://www.illumos.org/issues/8677 OpenZFS-commit: https://github.com/openzfs/openzfs/commit/17a49e15 Closes #6558
This commit is contained in:
committed by
Brian Behlendorf
parent
8d103d8856
commit
5b72a38d68
@@ -11,22 +11,31 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
|
||||
ZCP_ROOT=$STF_SUITE/tests/functional/channel_program
|
||||
|
||||
# <exitcode> <expected error string> <zfs program args>
|
||||
# e.g. log_program 0 $POOL foo.zcp arg1 arg2
|
||||
#
|
||||
# Note: In case of failure (log_fail) in this function
|
||||
# we delete the file passed as <input file> so the
|
||||
# test suite doesn't leak temp files on failures. So it
|
||||
# is expected that <input file> is a temp file and not
|
||||
# an installed file.
|
||||
#
|
||||
# <exitcode> <expected error string> <input file> <zfs program args>
|
||||
# e.g. log_program 0 "" tmp.7a12V $POOL foo.zcp arg1 arg2
|
||||
function log_program
|
||||
{
|
||||
typeset expectexit=$1
|
||||
shift
|
||||
typeset expecterror=$1
|
||||
shift
|
||||
typeset cmdargs=$@ tmpout=$(mktemp) tmperr=$(mktemp) tmpin=$(mktemp)
|
||||
typeset tmpin=$1
|
||||
shift
|
||||
typeset cmdargs=$@ tmpout=$(mktemp) tmperr=$(mktemp)
|
||||
|
||||
# Expected output/error filename is the same as the .zcp name
|
||||
typeset basename
|
||||
@@ -36,65 +45,195 @@ function log_program
|
||||
|
||||
log_note "running: zfs program $cmdargs:"
|
||||
|
||||
tee $tmpin | zfs program $cmdargs >$tmpout 2>$tmperr
|
||||
zfs program $cmdargs >$tmpout 2>$tmperr
|
||||
typeset ret=$?
|
||||
|
||||
log_note "input:\n$(cat $tmpin)"
|
||||
log_note "output:\n$(cat $tmpout)"
|
||||
log_note "error:\n$(cat $tmperr)"
|
||||
# verify correct return value
|
||||
|
||||
#
|
||||
# Verify correct return value
|
||||
#
|
||||
if [[ $ret -ne $expectexit ]]; then
|
||||
rm $tmpout $tmperr $tmpin
|
||||
log_fail "return mismatch: expected $expectexit, got $ret"
|
||||
fi
|
||||
|
||||
#
|
||||
# Check the output or reported error for successful or error returns,
|
||||
# respectively.
|
||||
#
|
||||
if [[ -f "$basename.out" ]] && [[ $expectexit -eq 0 ]]; then
|
||||
|
||||
outdiff=$(diff "$basename.out" "$tmpout")
|
||||
[[ $? -ne 0 ]] && log_fail "Output mismatch. Expected:\n" \
|
||||
"$(cat $basename.out)\nBut got:$(cat $tmpout)\n" \
|
||||
"Diff:\n$outdiff"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
output=$(cat $tmpout)
|
||||
rm $tmpout $tmperr $tmpin
|
||||
log_fail "Output mismatch. Expected:\n" \
|
||||
"$(cat $basename.out)\nBut got:\n$output\n" \
|
||||
"Diff:\n$outdiff"
|
||||
fi
|
||||
|
||||
elif [[ -f "$basename.err" ]] && [[ $expectexit -ne 0 ]]; then
|
||||
|
||||
outdiff=$(diff "$basename.err" "$tmperr")
|
||||
[[ $? -ne 0 ]] && log_fail "Error mismatch. Expected:\n" \
|
||||
"$(cat $basename.err)\nBut got:$(cat $tmpout)\n" \
|
||||
"Diff:\n$outdiff"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
outputerror=$(cat $tmperr)
|
||||
rm $tmpout $tmperr $tmpin
|
||||
log_fail "Error mismatch. Expected:\n" \
|
||||
"$(cat $basename.err)\nBut got:\n$outputerror\n" \
|
||||
"Diff:\n$outdiff"
|
||||
fi
|
||||
|
||||
elif [[ -n $expecterror ]] && [[ $expectexit -ne 0 ]]; then
|
||||
|
||||
grep -q "$expecterror" $tmperr || \
|
||||
log_fail "Error mismatch. Expected to contain:\n" \
|
||||
"$expecterror\nBut got:$(cat $tmpout)\n"
|
||||
grep -q "$expecterror" $tmperr
|
||||
if [[ $? -ne 0 ]]; then
|
||||
outputerror=$(cat $tmperr)
|
||||
rm $tmpout $tmperr $tmpin
|
||||
log_fail "Error mismatch. Expected to contain:\n" \
|
||||
"$expecterror\nBut got:\n$outputerror\n"
|
||||
fi
|
||||
|
||||
elif [[ $expectexit -ne 0 ]]; then
|
||||
#
|
||||
# If there's no expected output, error reporting is allowed to
|
||||
# vary, but ensure that we didn't fail silently.
|
||||
#
|
||||
[[ -z "$(cat $tmperr)" ]] && \
|
||||
log_fail "error with no stderr output"
|
||||
if [[ -z "$(cat $tmperr)" ]]; then
|
||||
rm $tmpout $tmperr $tmpin
|
||||
log_fail "error with no stderr output"
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# Clean up all temp files except $tmpin which is
|
||||
# reused for the second invocation of log_program.
|
||||
#
|
||||
rm $tmpout $tmperr
|
||||
}
|
||||
|
||||
#
|
||||
# Even though the command's arguments are passed correctly
|
||||
# to the log_must_program family of wrappers the majority
|
||||
# of the time, zcp scripts passed as HERE documents can
|
||||
# make things trickier (see comment within the function
|
||||
# below) in the ordering of the commands arguments and how
|
||||
# they are passed. Thus, with this function we reconstruct
|
||||
# them to ensure that they are passed properly.
|
||||
#
|
||||
function log_program_construct_args
|
||||
{
|
||||
typeset tmpin=$1
|
||||
shift
|
||||
|
||||
args=""
|
||||
i=0
|
||||
while getopts "nt:m:" opt; do
|
||||
case $opt in
|
||||
t) args="$args -t $OPTARG"; i=$(($i + 2)) ;;
|
||||
m) args="$args -m $OPTARG"; i=$(($i + 2)) ;;
|
||||
n) args="$args -n"; i=$(($i + 1)) ;;
|
||||
esac
|
||||
done
|
||||
shift $i
|
||||
|
||||
pool=$1
|
||||
shift
|
||||
|
||||
#
|
||||
# Catch HERE document if it exists and save it within our
|
||||
# temp file. The reason we do this is that since the
|
||||
# log_must_program wrapper calls zfs-program twice (once
|
||||
# for open context and once for syncing) the HERE doc
|
||||
# is consumed in the first invocation and the second one
|
||||
# does not have a program to run.
|
||||
#
|
||||
test -s /dev/stdin && cat > $tmpin
|
||||
|
||||
#
|
||||
# If $tmpin has contents it means that we consumed a HERE
|
||||
# doc and $1 currently holds "-" (a dash). If there is no
|
||||
# HERE doc and $tmpin is empty, then we copy the contents
|
||||
# of the original channel program to $tmpin.
|
||||
#
|
||||
[[ -s $tmpin ]] || cp $1 $tmpin
|
||||
shift
|
||||
|
||||
lua_args=$@
|
||||
|
||||
echo "$args $pool $tmpin $lua_args"
|
||||
}
|
||||
|
||||
#
|
||||
# Program should complete successfully
|
||||
# when run in either context.
|
||||
#
|
||||
function log_must_program
|
||||
{
|
||||
log_program 0 "" "$@"
|
||||
}
|
||||
typeset tmpin=$(mktemp)
|
||||
|
||||
function log_mustnot_program
|
||||
{
|
||||
log_program 1 "" "$@"
|
||||
}
|
||||
program_args=$(log_program_construct_args $tmpin $@)
|
||||
|
||||
log_program 0 "" $tmpin "-n $program_args"
|
||||
log_program 0 "" $tmpin "$program_args"
|
||||
|
||||
rm $tmpin
|
||||
}
|
||||
#
|
||||
# Program should error as expected in
|
||||
# the same way in both contexts.
|
||||
#
|
||||
function log_mustnot_checkerror_program
|
||||
{
|
||||
typeset expecterror=$1
|
||||
shift
|
||||
log_program 1 "$expecterror" "$@"
|
||||
typeset tmpin=$(mktemp)
|
||||
|
||||
program_args=$(log_program_construct_args $tmpin $@)
|
||||
|
||||
log_program 1 "$expecterror" $tmpin "-n $program_args"
|
||||
log_program 1 "$expecterror" $tmpin "$program_args"
|
||||
|
||||
rm $tmpin
|
||||
}
|
||||
|
||||
#
|
||||
# Program should fail when run in either
|
||||
# context.
|
||||
#
|
||||
function log_mustnot_program
|
||||
{
|
||||
log_mustnot_checkerror_program "" $@
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Program should error as expected in
|
||||
# open context but complete successfully
|
||||
# in syncing context.
|
||||
#
|
||||
function log_mustnot_checkerror_program_open
|
||||
{
|
||||
typeset expecterror=$1
|
||||
shift
|
||||
typeset tmpin=$(mktemp)
|
||||
|
||||
program_args=$(log_program_construct_args $tmpin $@)
|
||||
|
||||
log_program 1 "$expecterror" $tmpin "-n $program_args"
|
||||
log_program 0 "" $tmpin "$program_args"
|
||||
|
||||
rm $tmpin
|
||||
}
|
||||
|
||||
#
|
||||
# Program should complete successfully
|
||||
# when run in syncing context but fail
|
||||
# when attempted to run in open context.
|
||||
#
|
||||
function log_must_program_sync
|
||||
{
|
||||
log_mustnot_checkerror_program_open "requires passing sync=TRUE" $@
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ output_lines=$(log_must zfs program $TESTPOOL \
|
||||
#
|
||||
# Make sure we fail if the return is over the memory limit
|
||||
#
|
||||
log_mustnot_program $TESTPOOL -m 10000 \
|
||||
log_mustnot_program -m 10000 $TESTPOOL \
|
||||
$ZCP_ROOT/lua_core/tst.return_large.zcp
|
||||
|
||||
log_pass "Large return values work properly"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
verify_runnable "global"
|
||||
@@ -32,7 +32,7 @@ log_must zfs unmount $fs
|
||||
|
||||
log_must datasetexists $fs
|
||||
|
||||
log_must_program $TESTPOOL - $fs <<-EOF
|
||||
log_must_program_sync $TESTPOOL - $fs <<-EOF
|
||||
arg = ...
|
||||
fs = arg["argv"][1]
|
||||
err = zfs.sync.destroy(fs)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
verify_runnable "global"
|
||||
@@ -31,7 +31,7 @@ create_snapshot $TESTPOOL/$TESTFS $TESTSNAP
|
||||
|
||||
log_must snapexists $snap
|
||||
|
||||
log_must_program $TESTPOOL - $snap <<-EOF
|
||||
log_must_program_sync $TESTPOOL - $snap <<-EOF
|
||||
arg = ...
|
||||
snap = arg["argv"][1]
|
||||
err = zfs.sync.destroy(snap)
|
||||
|
||||
+5
-3
@@ -11,7 +11,7 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/channel_program/channel_common.kshlib
|
||||
@@ -40,8 +40,10 @@ set -A progs "zfs.sync.destroy(\"foo\", \"bar\")" \
|
||||
typeset -i i=0
|
||||
while (( i < ${#progs[*]} )); do
|
||||
log_note "running program: ${progs[i]}"
|
||||
# output should contain the usage message, which starts with "destroy{"
|
||||
echo ${progs[i]} | log_mustnot_checkerror_program "destroy{" $TESTPOOL -
|
||||
# output should contain the usage message, which contains "destroy{"
|
||||
log_mustnot_checkerror_program "destroy{" $TESTPOOL - <<-EOF
|
||||
${progs[i]}
|
||||
EOF
|
||||
((i = i + 1))
|
||||
done
|
||||
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/channel_program/channel_common.kshlib
|
||||
@@ -49,7 +49,7 @@ log_must zfs snapshot $clone@$snap
|
||||
# code and description, which should be EEXIST (17) and the name of the
|
||||
# conflicting snapshot.
|
||||
#
|
||||
log_must_program $TESTPOOL \
|
||||
log_must_program_sync $TESTPOOL \
|
||||
$ZCP_ROOT/synctask_core/tst.promote_conflict.zcp $clone
|
||||
|
||||
log_pass "Promoting a clone with a conflicting snapshot fails."
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/channel_program/channel_common.kshlib
|
||||
@@ -62,7 +62,7 @@ log_must zfs clone $snap2 $clone2
|
||||
|
||||
log_must zfs unmount -f $clone1
|
||||
|
||||
log_must_program $TESTPOOL - <<-EOF
|
||||
log_must_program_sync $TESTPOOL - <<-EOF
|
||||
assert(zfs.sync.promote("$clone2") == 0)
|
||||
assert(zfs.sync.promote("$clone2") == 0)
|
||||
assert(zfs.sync.destroy("$clone1") == 0)
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2016 by Delphix. All rights reserved.
|
||||
# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/channel_program/channel_common.kshlib
|
||||
@@ -40,7 +40,7 @@ log_must zfs create $fs
|
||||
log_must zfs snapshot $snap
|
||||
log_must zfs clone $snap $clone
|
||||
|
||||
log_must_program $TESTPOOL - <<-EOF
|
||||
log_must_program_sync $TESTPOOL - <<-EOF
|
||||
assert(zfs.sync.promote("$clone") == 0)
|
||||
EOF
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ log_must snapexists $snap1
|
||||
log_must snapexists $snap2
|
||||
log_must zfs unmount $fs
|
||||
|
||||
log_must_program $TESTPOOL - $fs $snap2 <<-EOF
|
||||
log_must_program_sync $TESTPOOL - $fs $snap2 <<-EOF
|
||||
arg = ...
|
||||
fs = arg["argv"][1]
|
||||
snap = arg["argv"][2]
|
||||
|
||||
@@ -37,7 +37,7 @@ log_must rm $file
|
||||
log_must snapexists $snap
|
||||
log_must zfs unmount $fs
|
||||
|
||||
log_must_program $TESTPOOL - $fs <<-EOF
|
||||
log_must_program_sync $TESTPOOL - $fs <<-EOF
|
||||
arg = ...
|
||||
fs = arg["argv"][1]
|
||||
err = zfs.sync.rollback(fs)
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ log_onexit cleanup
|
||||
|
||||
log_must zfs create $fs
|
||||
|
||||
log_must_program $TESTPOOL \
|
||||
log_must_program_sync $TESTPOOL \
|
||||
$ZCP_ROOT/synctask_core/tst.snapshot_destroy.zcp $fs
|
||||
|
||||
log_pass "Creating/destroying snapshots in one channel program works"
|
||||
|
||||
@@ -38,7 +38,8 @@ log_must zfs create $fs1
|
||||
log_must zfs create $fs2
|
||||
log_must zfs snapshot $fs1@snap1
|
||||
|
||||
log_must_program $TESTPOOL $ZCP_ROOT/synctask_core/tst.snapshot_neg.zcp $fs1 $fs2
|
||||
log_must_program_sync $TESTPOOL \
|
||||
$ZCP_ROOT/synctask_core/tst.snapshot_neg.zcp $fs1 $fs2
|
||||
|
||||
log_pass "zfs.sync.snapshot returns correct errors on invalid input"
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ for fs in $filesystems; do
|
||||
log_must zfs create $fs
|
||||
done
|
||||
|
||||
log_must_program $TESTPOOL \
|
||||
log_must_program_sync $TESTPOOL \
|
||||
$ZCP_ROOT/synctask_core/tst.snapshot_recursive.zcp $rootfs $snapname
|
||||
|
||||
#
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ log_onexit cleanup
|
||||
|
||||
log_must zfs create $fs
|
||||
|
||||
log_must_program $TESTPOOL \
|
||||
log_must_program_sync $TESTPOOL \
|
||||
$ZCP_ROOT/synctask_core/tst.snapshot_simple.zcp $fs $snapname
|
||||
|
||||
log_pass "Simple snapshotting works"
|
||||
|
||||
Reference in New Issue
Block a user