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 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 46 verify_runnable "global" 47 48 log_assert "Test properties are inherited correctly" 49 50 # 51 # Simple function to create specified datasets. 52 # 53 function 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 # 76 function 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 # 124 function 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 # 174 function 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 # 218 function 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 # 268 function 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 # 290 function 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 # 306 function 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 set -A prop "checksum" "" \ 375 "compression" "compress" \ 376 "atime" "" \ 377 "devices" "" \ 378 "exec" "" \ 379 "setuid" "" \ 380 "sharenfs" "" \ 381 "recordsize" "recsize" \ 382 "mountpoint" "" \ 383 "snapdir" "" \ 384 "aclmode" "" \ 385 "aclinherit" "" \ 386 "readonly" "rdonly" 387 388 # 389 # Note except for the mountpoint default value (which is handled in 390 # the routine itself), each property specified in the 'prop' array 391 # above must have a corresponding entry in the two arrays below. 392 # 393 394 set -A def_val "on" "off" "on" "on" "on" \ 395 "on" "off" "" \ 396 "" "hidden" "discard" "secure" \ 397 "off" 398 399 set -A local_val "off" "on" "off" "off" "off" \ 400 "off" "on" "" \ 401 "$TESTDIR" "visible" "groupmask" "discard" \ 402 "off" 403 404 # 405 # Global flag indicating whether the default record size had been 406 # read. 407 # 408 typeset def_recordsize=0 409 410 set -A config_files $(ls $STF_SUITE/tests/functional/inheritance/config*[1-9]*.cfg) 411 set -A state_files $(ls $STF_SUITE/tests/functional/inheritance/state*.cfg) 412 413 # 414 # Global list of datasets created. 415 # 416 list="" 417 418 typeset -i k=0 419 420 if [[ ${#config_files[*]} != ${#state_files[*]} ]]; then 421 log_fail "Must have the same number of config files " \ 422 " (${#config_files[*]}) and state files ${#state_files[*]}" 423 fi 424 425 while ((k < ${#config_files[*]})); do 426 default_cleanup_noexit 427 def_recordsize=0 428 429 log_note "Testing configuration ${config_files[k]}" 430 431 scan_config ${config_files[k]} 432 scan_state ${state_files[k]} 433 434 ((k = k + 1)) 435 done 436 437 log_pass "Properties correctly inherited as expected"