diff options
| author | Mike Gerdts <mike.gerdts@joyent.com> | 2019-06-27 22:24:45 +0000 |
|---|---|---|
| committer | Mike Gerdts <mike.gerdts@joyent.com> | 2019-07-08 12:32:35 +0000 |
| commit | b322ba367cdb75882f1cede5a8b22c7fd17ced94 (patch) | |
| tree | 43715c593fa1e561774314e82958461fa14405c4 | |
| parent | 374965e669006070cff81f276f2e13f54c6d9d51 (diff) | |
| download | illumos-joyent-b322ba367cdb75882f1cede5a8b22c7fd17ced94.tar.gz | |
OS-7867 zfs create needs dry-run
Reviewed by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
Approved by: Jerry Jelinek <jerry.jelinek@joyent.com>
| -rw-r--r-- | usr/src/cmd/zfs/zfs_main.c | 113 | ||||
| -rw-r--r-- | usr/src/test/zfs-tests/runfiles/smartos.run | 3 | ||||
| -rw-r--r-- | usr/src/test/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh | 169 |
3 files changed, 264 insertions, 21 deletions
diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c index 51b4260c71..525ba2022f 100644 --- a/usr/src/cmd/zfs/zfs_main.c +++ b/usr/src/cmd/zfs/zfs_main.c @@ -777,8 +777,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'. @@ -790,17 +790,33 @@ 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 following flags are private in SmartOS pending acceptance of interface + * changes by the external ZFS community. + * + * 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; zfs_handle_t *zhp = NULL; + 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; @@ -809,7 +825,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; @@ -825,6 +841,10 @@ zfs_do_create(int argc, char **argv) nomem(); volsize = intval; break; + case 'P': /* Private to SmartOS */ + verbose = B_TRUE; + parseable = B_TRUE; + break; case 'p': parents = B_TRUE; break; @@ -842,6 +862,9 @@ zfs_do_create(int argc, char **argv) intval) != 0) nomem(); break; + case 'n': /* Private to SmartOS */ + dryrun = B_TRUE; + break; case 'o': if (parseprop(props, optarg) != 0) goto error; @@ -849,6 +872,9 @@ zfs_do_create(int argc, char **argv) case 's': noreserve = B_TRUE; break; + case 'v': /* Private to SmartOS */ + verbose = B_TRUE; + break; case ':': (void) fprintf(stderr, gettext("missing size " "argument\n")); @@ -880,14 +906,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'; @@ -896,25 +917,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) { @@ -925,6 +952,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)) { /* @@ -936,8 +967,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), 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 */ diff --git a/usr/src/test/zfs-tests/runfiles/smartos.run b/usr/src/test/zfs-tests/runfiles/smartos.run index 6486a104db..f452ef7a84 100644 --- a/usr/src/test/zfs-tests/runfiles/smartos.run +++ b/usr/src/test/zfs-tests/runfiles/smartos.run @@ -96,7 +96,8 @@ tests = ['zfs_create_001_pos', 'zfs_create_002_pos', 'zfs_create_003_pos', 'zfs_create_004_pos', 'zfs_create_005_pos', 'zfs_create_006_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_encrypted', 'zfs_create_crypt_combos'] + 'zfs_create_013_pos', 'zfs_create_encrypted', 'zfs_create_crypt_combos', + 'zfs_create_dryrun'] [/opt/zfs-tests/tests/functional/cli_root/zfs_destroy] tests = ['zfs_destroy_001_pos', 'zfs_destroy_002_pos', 'zfs_destroy_003_pos', diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh new file mode 100644 index 0000000000..64b8296f46 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_create/zfs_create_dryrun.ksh @@ -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" |
