]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Add zfs create dryrun
authorMike Gerdts <mike.gerdts@joyent.com>
Tue, 16 Jul 2019 18:19:24 +0000 (13:19 -0500)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 16 Jul 2019 18:19:24 +0000 (11:19 -0700)
Adds the ability to sanity check zfs create arguments and to see the
value of any additional properties that will local to the dataset.  For
example, automation that may need to adjust quota on a parent filesystem
before creating a volume may call `zfs create -nP -V <size> <volume>` to
obtain the value of refreservation.  This adds the following options to
zfs create:

- -n dry-run (no-op)
- -v verbose
- -P parseable (implies verbose)

Reviewed-by: Ryan Moeller <ryan@ixsystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Matt Ahrens <matt@delphix.com>
Reviewed-by: Jerry Jelinek <jerry.jelinek@joyent.com>
Signed-off-by: Mike Gerdts <mike.gerdts@joyent.com>
Closes #8974

cmd/zfs/zfs_main.c
man/man8/zfs.8
tests/runfiles/linux.run
tests/zfs-tests/tests/functional/cli_root/zfs_create/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_verbose.ksh [new file with mode: 0755]

index 0ebd16f6d6a5ae0fc7f9219d31a97dabfabfd68c..53b76e25d6a63ae7b71becf1e57be87c23bc5884 100644 (file)
@@ -254,9 +254,9 @@ get_usage(zfs_help_t idx)
                return (gettext("\tclone [-p] [-o property=value] ... "
                    "<snapshot> <filesystem|volume>\n"));
        case HELP_CREATE:
-               return (gettext("\tcreate [-p] [-o property=value] ... "
+               return (gettext("\tcreate [-Pnpv] [-o property=value] ... "
                    "<filesystem>\n"
-                   "\tcreate [-ps] [-b blocksize] [-o property=value] ... "
+                   "\tcreate [-Pnpsv] [-b blocksize] [-o property=value] ... "
                    "-V <size> <volume>\n"));
        case HELP_DESTROY:
                return (gettext("\tdestroy [-fnpRrv] <filesystem|volume>\n"
@@ -867,8 +867,8 @@ usage:
 }
 
 /*
- * zfs create [-p] [-o prop=value] ... fs
- * zfs create [-ps] [-b blocksize] [-o prop=value] ... -V vol size
+ * zfs create [-Pnpv] [-o prop=value] ... fs
+ * zfs create [-Pnpsv] [-b blocksize] [-o prop=value] ... -V vol size
  *
  * Create a new dataset.  This command can be used to create filesystems
  * and volumes.  Snapshot creation is handled by 'zfs snapshot'.
@@ -880,16 +880,29 @@ usage:
  * SPA_VERSION_REFRESERVATION, we set a refreservation instead.
  *
  * The '-p' flag creates all the non-existing ancestors of the target first.
+ *
+ * The '-n' flag is no-op (dry run) mode.  This will perform a user-space sanity
+ * check of arguments and properties, but does not check for permissions,
+ * available space, etc.
+ *
+ * The '-v' flag is for verbose output.
+ *
+ * The '-P' flag is used for parseable output.  It implies '-v'.
  */
 static int
 zfs_do_create(int argc, char **argv)
 {
        zfs_type_t type = ZFS_TYPE_FILESYSTEM;
+       zpool_handle_t *zpool_handle = NULL;
+       nvlist_t *real_props = NULL;
        uint64_t volsize = 0;
        int c;
        boolean_t noreserve = B_FALSE;
        boolean_t bflag = B_FALSE;
        boolean_t parents = B_FALSE;
+       boolean_t dryrun = B_FALSE;
+       boolean_t verbose = B_FALSE;
+       boolean_t parseable = B_FALSE;
        int ret = 1;
        nvlist_t *props;
        uint64_t intval;
@@ -898,7 +911,7 @@ zfs_do_create(int argc, char **argv)
                nomem();
 
        /* check options */
-       while ((c = getopt(argc, argv, ":V:b:so:p")) != -1) {
+       while ((c = getopt(argc, argv, ":PV:b:nso:pv")) != -1) {
                switch (c) {
                case 'V':
                        type = ZFS_TYPE_VOLUME;
@@ -914,6 +927,10 @@ zfs_do_create(int argc, char **argv)
                                nomem();
                        volsize = intval;
                        break;
+               case 'P':
+                       verbose = B_TRUE;
+                       parseable = B_TRUE;
+                       break;
                case 'p':
                        parents = B_TRUE;
                        break;
@@ -931,6 +948,9 @@ zfs_do_create(int argc, char **argv)
                            intval) != 0)
                                nomem();
                        break;
+               case 'n':
+                       dryrun = B_TRUE;
+                       break;
                case 'o':
                        if (!parseprop(props, optarg))
                                goto error;
@@ -938,6 +958,9 @@ zfs_do_create(int argc, char **argv)
                case 's':
                        noreserve = B_TRUE;
                        break;
+               case 'v':
+                       verbose = B_TRUE;
+                       break;
                case ':':
                        (void) fprintf(stderr, gettext("missing size "
                            "argument\n"));
@@ -969,14 +992,9 @@ zfs_do_create(int argc, char **argv)
                goto badusage;
        }
 
-       if (type == ZFS_TYPE_VOLUME && !noreserve) {
-               zpool_handle_t *zpool_handle;
-               nvlist_t *real_props = NULL;
-               uint64_t spa_version;
+       if (dryrun || (type == ZFS_TYPE_VOLUME && !noreserve)) {
+               char msg[ZFS_MAX_DATASET_NAME_LEN * 2];
                char *p;
-               zfs_prop_t resv_prop;
-               char *strval;
-               char msg[1024];
 
                if ((p = strchr(argv[0], '/')) != NULL)
                        *p = '\0';
@@ -985,25 +1003,31 @@ zfs_do_create(int argc, char **argv)
                        *p = '/';
                if (zpool_handle == NULL)
                        goto error;
-               spa_version = zpool_get_prop_int(zpool_handle,
-                   ZPOOL_PROP_VERSION, NULL);
-               if (spa_version >= SPA_VERSION_REFRESERVATION)
-                       resv_prop = ZFS_PROP_REFRESERVATION;
-               else
-                       resv_prop = ZFS_PROP_RESERVATION;
 
                (void) snprintf(msg, sizeof (msg),
+                   dryrun ? gettext("cannot verify '%s'") :
                    gettext("cannot create '%s'"), argv[0]);
                if (props && (real_props = zfs_valid_proplist(g_zfs, type,
                    props, 0, NULL, zpool_handle, B_TRUE, msg)) == NULL) {
                        zpool_close(zpool_handle);
                        goto error;
                }
+       }
+
+       if (type == ZFS_TYPE_VOLUME && !noreserve) {
+               uint64_t spa_version;
+               zfs_prop_t resv_prop;
+               char *strval;
+
+               spa_version = zpool_get_prop_int(zpool_handle,
+                   ZPOOL_PROP_VERSION, NULL);
+               if (spa_version >= SPA_VERSION_REFRESERVATION)
+                       resv_prop = ZFS_PROP_REFRESERVATION;
+               else
+                       resv_prop = ZFS_PROP_RESERVATION;
 
                volsize = zvol_volsize_to_reservation(zpool_handle, volsize,
                    real_props);
-               nvlist_free(real_props);
-               zpool_close(zpool_handle);
 
                if (nvlist_lookup_string(props, zfs_prop_to_name(resv_prop),
                    &strval) != 0) {
@@ -1014,6 +1038,10 @@ zfs_do_create(int argc, char **argv)
                        }
                }
        }
+       if (zpool_handle != NULL) {
+               zpool_close(zpool_handle);
+               nvlist_free(real_props);
+       }
 
        if (parents && zfs_name_valid(argv[0], type)) {
                /*
@@ -1025,8 +1053,50 @@ zfs_do_create(int argc, char **argv)
                        ret = 0;
                        goto error;
                }
-               if (zfs_create_ancestors(g_zfs, argv[0]) != 0)
-                       goto error;
+               if (verbose) {
+                       (void) printf(parseable ? "create_ancestors\t%s\n" :
+                           dryrun ?  "would create ancestors of %s\n" :
+                           "create ancestors of %s\n", argv[0]);
+               }
+               if (!dryrun) {
+                       if (zfs_create_ancestors(g_zfs, argv[0]) != 0) {
+                               goto error;
+                       }
+               }
+       }
+
+       if (verbose) {
+               nvpair_t *nvp = NULL;
+               (void) printf(parseable ? "create\t%s\n" :
+                   dryrun ? "would create %s\n" : "create %s\n", argv[0]);
+               while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL) {
+                       uint64_t uval;
+                       char *sval;
+
+                       switch (nvpair_type(nvp)) {
+                       case DATA_TYPE_UINT64:
+                               VERIFY0(nvpair_value_uint64(nvp, &uval));
+                               (void) printf(parseable ?
+                                   "property\t%s\t%llu\n" : "\t%s=%llu\n",
+                                   nvpair_name(nvp), (u_longlong_t)uval);
+                               break;
+                       case DATA_TYPE_STRING:
+                               VERIFY0(nvpair_value_string(nvp, &sval));
+                               (void) printf(parseable ?
+                                   "property\t%s\t%s\n" : "\t%s=%s\n",
+                                   nvpair_name(nvp), sval);
+                               break;
+                       default:
+                               (void) fprintf(stderr, "property '%s' "
+                                   "has illegal type %d\n",
+                                   nvpair_name(nvp), nvpair_type(nvp));
+                               abort();
+                       }
+               }
+       }
+       if (dryrun) {
+               ret = 0;
+               goto error;
        }
 
        /* pass to libzfs */
index 3fc142f5eb8c67c496e58cdcf5046da15963665b..cbbd76503ab660cb4d1cb89dc2ec8a1de856bef8 100644 (file)
@@ -28,9 +28,9 @@
 .\" Copyright (c) 2014 Integros [integros.com]
 .\" Copyright 2019 Richard Laager. All rights reserved.
 .\" Copyright 2018 Nexenta Systems, Inc.
-.\" Copyright 2018 Joyent, Inc.
+.\" Copyright 2019 Joyent, Inc.
 .\"
-.Dd April 30, 2019
+.Dd June 30, 2019
 .Dt ZFS 8 SMM
 .Os Linux
 .Sh NAME
 .Fl ?V
 .Nm
 .Cm create
-.Op Fl p
+.Op Fl Pnpv
 .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ...
 .Ar filesystem
 .Nm
 .Cm create
-.Op Fl ps
+.Op Fl Pnpsv
 .Op Fl b Ar blocksize
 .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ...
 .Fl V Ar size Ar volume
@@ -2556,7 +2556,7 @@ subcommand.
 .It Xo
 .Nm
 .Cm create
-.Op Fl p
+.Op Fl Pnpv
 .Oo Fl o Ar property Ns = Ns Ar value Oc Ns ...
 .Ar filesystem
 .Xc
@@ -2585,6 +2585,48 @@ Any property specified on the command line using the
 .Fl o
 option is ignored.
 If the target filesystem already exists, the operation completes successfully.
+.It Fl n
+Do a dry-run
+.Pq Qq No-op
+creation.
+No datasets will be created.
+This is useful in conjunction with the
+.Fl v
+or
+.Fl P
+flags to validate properties that are passed via
+.Fl o
+options and those implied by other options.
+The actual dataset creation can still fail due to insufficient privileges or
+available capacity.
+.It Fl P
+Print machine-parsable verbose information about the created dataset.
+Each line of output contains a key and one or two values, all separated by tabs.
+The
+.Sy create_ancestors
+and 
+.Sy create
+keys have
+.Em filesystem
+as their only value.
+The
+.Sy create_ancestors
+key only appears if the
+.Fl p
+option is used.
+The
+.Sy property
+key has two values, a property name that property's value.
+The
+.Sy property
+key may appear zero or more times, once for each property that will be set local
+to
+.Em filesystem
+due to the use of the
+.Fl o
+option.
+.It Fl v
+Print verbose information about the created dataset.
 .El
 .It Xo
 .Nm
@@ -2641,6 +2683,52 @@ See
 in the
 .Sx Native Properties
 section for more information about sparse volumes.
+.It Fl n
+Do a dry-run
+.Pq Qq No-op
+creation.
+No datasets will be created.
+This is useful in conjunction with the
+.Fl v
+or
+.Fl P
+flags to validate properties that are passed via
+.Fl o
+options and those implied by other options.
+The actual dataset creation can still fail due to insufficient privileges or
+available capacity.
+.It Fl P
+Print machine-parsable verbose information about the created dataset.
+Each line of output contains a key and one or two values, all separated by tabs.
+The
+.Sy create_ancestors
+and 
+.Sy create
+keys have
+.Em volume
+as their only value.
+The
+.Sy create_ancestors
+key only appears if the
+.Fl p
+option is used.
+The
+.Sy property
+key has two values, a property name that property's value.
+The
+.Sy property
+key may appear zero or more times, once for each property that will be set local
+to
+.Em volume
+due to the use of the
+.Fl b
+or
+.Fl o
+options, as well as
+.Sy refreservation
+if the volume is not sparse.
+.It Fl v
+Print verbose information about the created dataset.
 .El
 .It Xo
 .Nm
index 36ab5ef22b0ad332e8459976c9928aacd4d7cf9f..4a0e151f442dbb3ded0a794d5fabc32ffc4ebe2e 100644 (file)
@@ -143,7 +143,7 @@ tests = ['zfs_create_001_pos', 'zfs_create_002_pos', 'zfs_create_003_pos',
     'zfs_create_007_pos', 'zfs_create_008_neg', 'zfs_create_009_neg',
     'zfs_create_010_neg', 'zfs_create_011_pos', 'zfs_create_012_pos',
     'zfs_create_013_pos', 'zfs_create_014_pos', 'zfs_create_encrypted',
-    'zfs_create_crypt_combos']
+    'zfs_create_crypt_combos', 'zfs_create_dryrun', 'zfs_create_verbose']
 tags = ['functional', 'cli_root', 'zfs_create']
 
 [tests/functional/cli_root/zfs_destroy]
index a36d021614237150355ad201a81541d7efd000b9..cb65507ae7112b5531dfb88e5a7ad40c425832b5 100644 (file)
@@ -17,7 +17,9 @@ dist_pkgdata_SCRIPTS = \
        zfs_create_013_pos.ksh \
        zfs_create_014_pos.ksh \
        zfs_create_encrypted.ksh \
-       zfs_create_crypt_combos.ksh
+       zfs_create_crypt_combos.ksh \
+       zfs_create_dryrun.ksh \
+       zfs_create_verbose.ksh
 
 dist_pkgdata_DATA = \
        properties.kshlib \
diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh
new file mode 100755 (executable)
index 0000000..64b8296
--- /dev/null
@@ -0,0 +1,169 @@
+#!/bin/ksh -p
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/cli_root/zfs_create/zfs_create_common.kshlib
+
+#
+# DESCRIPTION:
+# zfs create -n should perform basic sanity checking but should never create a
+# dataset.  If -v and/or -P are used, it should verbose about what would be
+# created if sanity checks pass.
+#
+# STRATEGY:
+# 1. Attempt to create a file system and a volume using various combinations of
+#    -n with -v and -P.
+#
+
+verify_runnable "both"
+
+#
+# Verifies that valid commands with -n and without -[vP]:
+# - succeed
+# - do not create a dataset
+# - do not generate output
+#
+function dry_create_no_output
+{
+       typeset -a cmd=(zfs create -n "$@")
+
+       log_note "$0: ${cmd[@]}"
+       log_must "${cmd[@]}"
+       datasetexists "$TESTPOOL/$TESTFS1" &&
+           log_fail "$TESTPOOL/$TESTFS1 unexpectedly created by '${cmd[@]}'"
+       typeset out=$("${cmd[@]}" 2>&1)
+       [[ -z "$out" ]] ||
+           log_fail "unexpected output '$out' from '${cmd[@]}'"
+}
+
+#
+# Verifies that commands with invalid properties or invalid property values
+# - fail
+# - do not create a dataset
+# - generate a message on stderr
+#
+function dry_create_error
+{
+       typeset -a cmd=(zfs create -n "$@")
+
+       log_note "$0: ${cmd[@]}"
+       log_mustnot "${cmd[@]}"
+       datasetexists "$TESTPOOL/$TESTFS1" &&
+           log_fail "$TESTPOOL/$TESTFS1 unexpectedly created by '${cmd[@]}'"
+       typeset out=$("${cmd[@]}" 2>&1 >/dev/null)
+       [[ -z "$out" ]] &&
+           log_fail "expected an error message but got none from '${cmd[@]}'"
+}
+
+#
+# Verifies that dry-run commands with parseable output
+# - succeed
+# - do not create datasets
+# - generate parseable output on stdout
+# - output matches expectations
+#
+function dry_create_parseable
+{
+       typeset -n exp=$1
+       shift
+       typeset -a cmd=(zfs create -Pn "$@")
+       typeset ds=${cmd[${#cmd[@]} - 1]}
+       typeset out
+       typeset -a toks
+       typeset -a props
+       typeset found_create=false
+
+       log_note "$0: ${cmd[@]}"
+       out=$("${cmd[@]}")
+       (( $? == 0 )) ||
+           log_fail "unexpected failure getting stdout from '${cmd[@]}'"
+       datasetexists "$TESTPOOL/$TESTFS1" &&
+           log_fail "$TESTPOOL/$TESTFS1 unexpectedly created by '${cmd[@]}'"
+       echo "$out" | while IFS=$'\t' read -A toks; do
+               log_note "verifying ${toks[@]}"
+               case ${toks[0]} in
+               create)
+                       log_must test "${#toks[@]}" -eq 2
+                       log_must test "${toks[1]}" == "$ds"
+                       found_create="yes, I found create"
+                       ;;
+               property)
+                       log_must test "${#toks[@]}" -eq 3
+                       typeset prop=${toks[1]}
+                       typeset val=${toks[2]}
+                       if [[ -z "${exp[$prop]}" ]]; then
+                               log_fail "unexpectedly got property '$prop'"
+                       fi
+                       # We may not know the exact value a property will take
+                       # on.  This is the case for at least refreservation.
+                       if [[ ${exp[$prop]} != "*" ]]; then
+                               log_must test "${exp[$prop]}" == "$val"
+                       fi
+                       unset exp[$prop]
+                       ;;
+               *)
+                       log_fail "Unexpected line ${toks[@]}"
+                       ;;
+               esac
+       done
+
+       log_must test "$found_create" == "yes, I found create"
+       log_must test "extra props: ${!exp[@]}" == "extra props: "
+}
+
+function cleanup
+{
+       if datasetexists "$TESTPOOL/$TESTFS1"; then
+               log_must zfs destroy -r "$TESTPOOL/$TESTFS1"
+       fi
+}
+log_onexit cleanup
+
+log_assert "zfs create -n creates nothing but can describe what would be" \
+       "created"
+
+# Typical creations should succeed
+dry_create_no_output "$TESTPOOL/$TESTFS1"
+dry_create_no_output -V 10m "$TESTPOOL/$TESTFS1"
+# It shouldn't do a space check right now
+dry_create_no_output -V 100t "$TESTPOOL/$TESTFS1"
+# It shouldn't create parent datasets either
+dry_create_no_output -p "$TESTPOOL/$TESTFS1/$TESTFS2"
+dry_create_no_output -pV 10m "$TESTPOOL/$TESTFS1/$TESTFS2"
+
+# Various invalid properties should be recognized and result in an error
+dry_create_error -o nosuchprop=42 "$TESTPOOL/$TESTFS1"
+dry_create_error -b 1234 -V 10m  "$TESTPOOL/$TESTFS1"
+
+# Parseable output should be parseable.
+typeset -A expect
+expect=([compression]=on)
+dry_create_parseable expect -o compression=on "$TESTPOOL/$TESTFS1"
+
+# Sparse volumes should not get a gratuitous refreservation
+expect=([volblocksize]=4096 [volsize]=$((1024 * 1024 * 10)))
+dry_create_parseable expect -b 4k -V 10m -s "$TESTPOOL/$TESTFS1"
+
+# Non-sparse volumes should have refreservation
+expect=(
+    [volblocksize]=4096
+    [volsize]=$((1024 * 1024 * 10))
+    [refreservation]="*"
+)
+dry_create_parseable expect -b 4k -V 10m "$TESTPOOL/$TESTFS1"
+
+log_pass "zfs create -n creates nothing but can describe what would be" \
+       "created"
diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_verbose.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_verbose.ksh
new file mode 100755 (executable)
index 0000000..a07ccc7
--- /dev/null
@@ -0,0 +1,165 @@
+#!/bin/ksh -p
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/cli_root/zfs_create/zfs_create_common.kshlib
+
+#
+# DESCRIPTION:
+# zfs create -P without -n should be verbose about dataset creation.
+#
+# STRATEGY:
+# 1. Attempt to create a file system and a volume using various properties
+#    and -P
+# 2. Exercise the combination of -p and -P.
+#
+
+verify_runnable "both"
+
+#
+# Verifies that non dry-run commands with parseable output
+# - succeed
+# - create datasets
+# - generate parseable output on stdout
+# - output matches expectations
+#
+function dry_create_parseable
+{
+       typeset -n exp=$1
+       shift
+       typeset -a cmd=(zfs create -P "$@")
+       typeset ds=${cmd[${#cmd[@]} - 1]}
+       typeset out
+       typeset -a toks
+       typeset -a props
+       typeset found_create=false
+       typeset create_ancestors=
+       typeset opt
+
+       # Parse the arguments to see if -p was used.
+       while getopts :PV:b:ospv opt; do
+               case $opt in
+               p)      create_ancestors=needed ;;
+               *)      continue ;;
+               esac
+       done
+
+       log_note "$0: ${cmd[@]}"
+       out=$("${cmd[@]}")
+       (( $? == 0 )) ||
+           log_fail "unexpected failure getting stdout from '${cmd[@]}'"
+       datasetexists "$TESTPOOL/$TESTFS1" ||
+           log_fail "$TESTPOOL/$TESTFS1 unexpectedly created by '${cmd[@]}'"
+       echo "$out" | while IFS=$'\t' read -A toks; do
+               log_note "verifying ${toks[@]}"
+               case ${toks[0]} in
+               create_ancestors)
+                       case "$create_ancestors" in
+                       needed)
+                               log_must test "${toks[1]}" == "$ds"
+                               create_ancestors="found ${toks[1]}"
+                               ;;
+                       found*)
+                               log_fail "multiple ancestor creation" \
+                                   "$create_ancestors and ${toks[1]}"
+                               ;;
+                       "")
+                               log_fail "unexpected create_ancestors"
+                               ;;
+                       *)
+                               log_fail "impossible error: fix the test"
+                               ;;
+                       esac
+                       ;;
+               create)
+                       log_must test "${#toks[@]}" -eq 2
+                       log_must test "${toks[1]}" == "$ds"
+                       found_create="yes, I found create"
+                       ;;
+               property)
+                       log_must test "${#toks[@]}" -eq 3
+                       typeset prop=${toks[1]}
+                       typeset val=${toks[2]}
+                       if [[ -z "${exp[$prop]}" ]]; then
+                               log_fail "unexpectedly got property '$prop'"
+                       fi
+                       # We may not know the exact value a property will take
+                       # on.  This is the case for at least refreservation.
+                       if [[ ${exp[$prop]} != "*" ]]; then
+                               log_must test "${exp[$prop]}" == "$val"
+                       fi
+                       unset exp[$prop]
+                       ;;
+               *)
+                       log_fail "Unexpected line ${toks[@]}"
+                       ;;
+               esac
+       done
+
+       log_must test "$found_create" == "yes, I found create"
+       log_must test "extra props: ${!exp[@]}" == "extra props: "
+
+       case "$create_ancestors" in
+       "")
+               log_must_busy zfs destroy "$ds"
+               ;;
+       "found $ds")
+               log_must_busy zfs destroy -r "$(echo "$ds" | cut -d/ -f1-2)"
+               ;;
+       needed)
+               log_fail "Expected but did not find create_ancestors"
+               ;;
+       *)
+               log_fail "Unexpected value for create_ancestors:" \
+                   "$create_ancestors"
+               ;;
+       esac
+}
+
+function cleanup
+{
+       if datasetexists "$TESTPOOL/$TESTFS1"; then
+               log_must_busy zfs destroy -r "$TESTPOOL/$TESTFS1"
+       fi
+}
+log_onexit cleanup
+
+log_assert "zfs create -v creates datasets verbosely"
+
+# Parseable output should be parseable.
+typeset -A expect
+expect=([compression]=on)
+dry_create_parseable expect -o compression=on "$TESTPOOL/$TESTFS1"
+
+# Ancestor creation with -p should emit relevant line
+expect=([compression]=on)
+dry_create_parseable expect -p -o compression=on "$TESTPOOL/$TESTFS1"
+expect=([compression]=on)
+dry_create_parseable expect -p -o compression=on "$TESTPOOL/$TESTFS1/$TESTVOL"
+
+# Sparse volumes should not get a gratuitous refreservation
+expect=([volblocksize]=4096 [volsize]=$((1024 * 1024 * 10)))
+dry_create_parseable expect -b 4k -V 10m -s "$TESTPOOL/$TESTFS1"
+
+# Non-sparse volumes should have refreservation
+expect=(
+    [volblocksize]=4096
+    [volsize]=$((1024 * 1024 * 10))
+    [refreservation]="*"
+)
+dry_create_parseable expect -b 4k -V 10m "$TESTPOOL/$TESTFS1"
+
+log_pass "zfs create -v creates datasets verbosely"