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 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 374set -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 394set -A def_val "on" "off" "on" "on" "on" \ 395 "on" "off" "" \ 396 "" "hidden" "discard" "secure" \ 397 "off" 398 399set -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# 408typeset def_recordsize=0 409 410set -A config_files $(ls $STF_SUITE/tests/functional/inheritance/config*[1-9]*.cfg) 411set -A state_files $(ls $STF_SUITE/tests/functional/inheritance/state*.cfg) 412 413# 414# Global list of datasets created. 415# 416list="" 417 418typeset -i k=0 419 420if [[ ${#config_files[*]} != ${#state_files[*]} ]]; then 421 log_fail "Must have the same number of config files " \ 422 " (${#config_files[*]}) and state files ${#state_files[*]}" 423fi 424 425while ((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)) 435done 436 437log_pass "Properties correctly inherited as expected" 438