xref: /illumos-gate/usr/src/test/zfs-tests/tests/functional/inheritance/inherit_001_pos.ksh (revision 5f82aa32fbc5dc2c59bca6ff315f44a4c4c9ea86)
1#! /bin/ksh -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26
27#
28# Copyright (c) 2013, 2016 by Delphix. All rights reserved.
29#
30
31. $STF_SUITE/include/libtest.shlib
32. $STF_SUITE/tests/functional/inheritance/inherit.kshlib
33
34#
35# DESCRIPTION:
36# Test that properties are correctly inherited using 'zfs set',
37# 'zfs inherit' and 'zfs inherit -r'.
38#
39# STRATEGY:
40# 1) Read a configX.cfg file and create the specified datasets
41# 2) Read a stateX.cfg file and execute the commands within it
42# and verify that the properties have the correct values
43# 3) Repeat steps 1-2 for each configX and stateX files found.
44#
45
46verify_runnable "global"
47
48log_assert "Test properties are inherited correctly"
49
50#
51# Simple function to create specified datasets.
52#
53function create_dataset { #name type disks
54	typeset dataset=$1
55	typeset type=$2
56	typeset disks=$3
57
58	if [[ $type == "POOL" ]]; then
59		create_pool "$dataset" "$disks"
60	elif [[ $type == "CTR" ]]; then
61		log_must zfs create $dataset
62		log_must zfs set canmount=off $dataset
63	elif [[ $type == "FS" ]]; then
64		log_must zfs create $dataset
65	else
66		log_fail "Unrecognised type $type"
67	fi
68
69	list="$list $dataset"
70}
71
72#
73# Function to walk through all the properties in a
74# dataset, setting them to a 'local' value if required.
75#
76function init_props { #dataset init_code
77	typeset dataset=$1
78	typeset init_code=$2
79	typeset dir=$3
80
81	typeset -i i=0
82
83	#
84	# Though the effect of '-' and 'default' is the same we
85	# call them out via a log_note to aid in debugging the
86	# config files
87	#
88	if [[ $init_code == "-" ]]; then
89		log_note "Leaving properties for $dataset unchanged."
90		[[ $def_recordsize == 0 ]] && \
91		    update_recordsize $dataset $init_code
92		return;
93	elif [[ $init_code == "default" ]]; then
94		log_note "Leaving properties for $dataset at default values."
95		[[ $def_recordsize == 0 ]] && \
96		    update_recordsize $dataset $init_code
97		return;
98	elif [[ $init_code == "local" ]]; then
99		log_note "Setting properties for $dataset to local values."
100		while (( i <  ${#prop[*]} )); do
101			if [[ ${prop[i]} == "recordsize" ]]; then
102				update_recordsize $dataset $init_code
103			else
104				if [[ ${prop[i]} == "mountpoint" ]]; then
105					set_n_verify_prop ${prop[i]} \
106					    ${local_val[((i/2))]}.$dir $dataset
107				else
108					set_n_verify_prop ${prop[i]} \
109					    ${local_val[((i/2))]} $dataset
110				fi
111			fi
112
113			((i = i + 2))
114		done
115	else
116		log_fail "Unrecognised init code $init_code"
117	fi
118}
119
120#
121# We enter this function either to update the recordsize value
122# in the default array, or to update the local value array.
123#
124function update_recordsize { #dataset init_code
125	typeset dataset=$1
126	typeset init_code=$2
127	typeset idx=0
128	typeset record_val
129
130	#
131	# First need to find where the recordsize property is
132	# located in the arrays
133	#
134	while (( idx <  ${#prop[*]} )); do
135		[[ ${prop[idx]} == "recordsize" ]] && break
136
137		((idx = idx + 2))
138	done
139
140	((idx = idx / 2))
141	record_val=`get_prop recordsize $dataset`
142	if [[ $init_code == "-" || $init_code == "default" ]]; then
143		def_val[idx]=$record_val
144		def_recordsize=1
145	elif [[ $init_code == "local" ]]; then
146		log_must zfs set recordsize=$record_val $dataset
147		local_val[idx]=$record_val
148	fi
149}
150
151#
152# The mountpoint property is slightly different from other properties and
153# so is handled here. For all other properties if they are set to a specific
154# value at a higher level in the data hierarchy (i.e. checksum=on) then that
155# value propogates down the hierarchy unchanged, with the source field being
156# set to 'inherited from <higher dataset>'.
157#
158# The mountpoint property is different in that while the value propogates
159# down the hierarchy, the value at each level is determined by a combination
160# of the top-level value and the current level in the hierarchy.
161#
162# For example consider the case where we have a pool (called pool1), containing
163# a dataset (ctr) which in turn contains a filesystem (fs). If we set the
164# mountpoint of the pool to '/mnt2' then the mountpoints for the dataset and
165# filesystem are '/mnt2/ctr' and /mnt2/ctr/fs' respectively, with the 'source'
166# field being set to 'inherited from pool1'.
167#
168# So at the filesystem level to calculate what our mountpoint property should
169# be set to we walk back up the hierarchy sampling the mountpoint property at
170# each level and forming up the expected mountpoint value piece by piece until
171# we reach the level specified in the 'source' field, which in this example is
172# the top-level pool.
173#
174function get_mntpt_val #dataset src index
175{
176	typeset dataset=$1
177	typeset src=$2
178	typeset idx=$3
179	typeset new_path=""
180	typeset dset
181	typeset mntpt=""
182
183	if [[ $src == "local" ]]; then
184		# Extract mount points specific to datasets
185		if [[ $dataset == "TESTPOOL" ]]; then
186			mntpt=${local_val[idx]}.1
187		elif [[ $dataset == "TESTPOOL/TESTCTR" ]]; then
188			mntpt=${local_val[idx]}.2
189		else
190			mntpt=${local_val[idx]}.3
191		fi
192	elif [[ $src == "default" ]]; then
193		mntpt="/$dataset"
194	else
195		# Walk back up the hierarchy building up the
196		# expected mountpoint property value.
197		obj_name=${dataset##*/}
198
199		while [[ $src != $dataset ]]; do
200			dset=${dataset%/*}
201
202			mnt_val=`get_prop mountpoint $dset`
203
204			mod_prop_val=${mnt_val##*/}
205			new_path="/"$mod_prop_val$new_path
206			dataset=$dset
207		done
208
209		mntpt=$new_path"/"$obj_name
210	fi
211	echo $mntpt
212}
213
214#
215# Simple function to verify that a property has the
216# expected value.
217#
218function verify_prop_val #property dataset src index
219{
220	typeset prop=$1
221	typeset dataset=$2
222	typeset src=$3
223	typeset idx=$4
224	typeset new_path=""
225	typeset dset
226	typeset exp_val
227	typeset prop_val
228
229	prop_val=`get_prop $prop $dataset`
230
231	# mountpoint property is handled as a special case
232	if [[ $prop == "mountpoint" ]]; then
233		exp_val=`get_mntpt_val $dataset $src $idx`
234	else
235		if [[ $src == "local" ]]; then
236			exp_val=${local_val[idx]}
237		elif [[ $src == "default" ]]; then
238			exp_val=${def_val[idx]}
239		else
240			#
241			# We are inheriting the value from somewhere
242			# up the hierarchy.
243			#
244			exp_val=`get_prop $prop $src`
245		fi
246	fi
247
248	if [[ $prop_val != $exp_val ]]; then
249		# After putback PSARC/2008/231 Apr,09,2008,
250		# the default value of aclinherit has changed to be
251		# 'restricted' instead of 'secure',
252		# but the old interface of 'secure' still exist
253
254		if [[ $prop != "aclinherit" || \
255		    $exp_val != "secure" || \
256		    $prop_val != "restricted" ]]; then
257
258			log_fail "$prop of $dataset is [$prop_val] rather "\
259			    "than [$exp_val]"
260		fi
261	fi
262}
263
264#
265# Function to read the configX.cfg files and create the specified
266# dataset hierarchy
267#
268function scan_config { #config-file
269	typeset config_file=$1
270
271	DISK=${DISKS%% *}
272
273	list=""
274	typeset -i mount_dir=1
275
276	grep "^[^#]" $config_file | {
277		while read name type init ; do
278			create_dataset $name $type $DISK
279			init_props $name $init $mount_dir
280			((mount_dir = mount_dir + 1))
281		done
282	}
283}
284
285#
286# Function to check an exit flag, calling log_fail if that exit flag
287# is non-zero. Can be used from code that runs in a tight loop, which
288# would otherwise result in a lot of journal output.
289#
290function check_failure { # int status, error message to use
291
292	typeset -i exit_flag=$1
293	error_message=$2
294
295	if [[ $exit_flag -ne 0 ]]; then
296		log_fail "$error_message"
297	fi
298}
299
300
301#
302# Main function. Executes the commands specified in the stateX.cfg
303# files and then verifies that all the properties have the correct
304# values and 'source' fields.
305#
306function scan_state { #state-file
307	typeset state_file=$1
308	typeset -i i=0
309	typeset -i j=0
310
311	log_note "Reading state from $state_file"
312
313	while ((i <  ${#prop[*]})); do
314		grep "^[^#]" $state_file | {
315			while IFS=: read target op; do
316				#
317				# The user can if they wish specify that no
318				# operation be performed (by specifying '-'
319				# rather than a command). This is not as
320				# useless as it sounds as it allows us to
321				# verify that the dataset hierarchy has been
322				# set up correctly as specified in the
323				# configX.cfg file (which includes 'set'ting
324				# properties at a higher level and checking
325				# that they propogate down to the lower levels.
326				#
327				# Note in a few places here, we use
328				# check_failure, rather than log_must - this
329				# substantially reduces journal output.
330				#
331				if [[ $op == "-" ]]; then
332					log_note "No operation specified"
333				else
334					export __ZFS_POOL_RESTRICT="TESTPOOL"
335					log_must zfs unmount -a
336					unset __ZFS_POOL_RESTRICT
337
338					for p in ${prop[i]} ${prop[((i+1))]}; do
339						zfs $op $p $target
340						ret=$?
341						check_failure $ret "zfs $op $p \
342						    $target"
343					done
344				fi
345				for check_obj in $list; do
346					read init_src final_src
347
348					for p in ${prop[i]} ${prop[((i+1))]}; do
349					# check_failure to keep journal small
350						verify_prop_src $check_obj $p \
351						    $final_src
352						ret=$?
353						check_failure $ret "verify" \
354						    "_prop_src $check_obj $p" \
355						    "$final_src"
356
357					# Again, to keep journal size down.
358						verify_prop_val $p $check_obj \
359						    $final_src $j
360						ret=$?
361						check_failure $ret "verify" \
362						    "_prop_val $check_obj $p" \
363						    "$final_src"
364					done
365				done
366			done
367		}
368		((i = i + 2))
369		((j = j + 1))
370	done
371}
372
373#
374# Note that we keep this list relatively short so that this test doesn't
375# time out (after taking more than 10 minutes).
376#
377set -A prop "checksum" "" \
378	"compression" "" \
379	"atime" "" \
380	"sharenfs" "" \
381	"recordsize" "recsize" \
382	"mountpoint" "" \
383	"snapdir" "" \
384	"aclmode" "" \
385	"readonly" ""
386
387#
388# Note except for the mountpoint default value (which is handled in
389# the routine itself), each property specified in the 'prop' array
390# above must have a corresponding entry in the two arrays below.
391#
392
393set -A def_val "on" "off" "on" \
394	"off" "" \
395	"" "hidden" "discard" \
396	"off"
397
398set -A local_val "off" "on" "off" \
399	"on" "" \
400	"$TESTDIR" "visible" "groupmask" \
401	"off"
402
403#
404# Global flag indicating whether the default record size had been
405# read.
406#
407typeset def_recordsize=0
408
409set -A config_files $(ls $STF_SUITE/tests/functional/inheritance/config*[1-9]*.cfg)
410set -A state_files $(ls $STF_SUITE/tests/functional/inheritance/state*.cfg)
411
412#
413# Global list of datasets created.
414#
415list=""
416
417typeset -i k=0
418
419if [[ ${#config_files[*]} != ${#state_files[*]} ]]; then
420	log_fail "Must have the same number of config files " \
421	    " (${#config_files[*]}) and state files ${#state_files[*]}"
422fi
423
424while ((k < ${#config_files[*]})); do
425	default_cleanup_noexit
426	def_recordsize=0
427
428	log_note "Testing configuration ${config_files[k]}"
429
430	scan_config ${config_files[k]}
431	scan_state ${state_files[k]}
432
433	((k = k + 1))
434done
435
436log_pass "Properties correctly inherited as expected"
437