Whamcloud - gitweb
LU-11171 tests: set parameters for racer_on_nfs
[fs/lustre-release.git] / lustre / tests / functions.sh
1 #!/bin/bash
2
3 # Simple function used by run_*.sh scripts
4
5 assert_env() {
6     local failed=""
7     for name in $@; do
8         if [ -z "${!name}" ]; then
9             echo "$0: $name must be set"
10             failed=1
11         fi
12     done
13     [ $failed ] && exit 1 || true
14 }
15
16 # lrepl - Lustre test Read-Eval-Print Loop.
17 #
18 # This function implements a REPL for the Lustre test framework.  It
19 # doesn't exec an actual shell because the user may want to inspect
20 # variables and use functions from the test framework.
21 lrepl() {
22     local line
23     local rawline
24     local prompt
25
26     cat <<EOF
27         This is an interactive read-eval-print loop interactive shell
28         simulation that you can use to debug failing tests.  You can
29         enter most bash command lines (see notes below).
30
31         Use this REPL to inspect variables, set them, call test
32         framework shell functions, etcetera.
33
34         'exit' or EOF to exit this shell.
35
36         set \$retcode to 0 to cause the assertion failure that
37         triggered this REPL to be ignored.
38
39         Examples:
40             do_facet ost1 lctl get_param ost.*.ost.threads_*
41             do_rpc_nodes \$OSTNODES unload_modules
42
43         NOTES:
44             All but the last line of multi-line statements or blocks
45             must end in a backslash.
46
47             "Here documents" are not supported.
48
49             History is not supported, but command-line editing is.
50
51 EOF
52
53     # Prompt escapes don't work in read -p, sadly.
54     prompt=":test_${testnum:-UNKNOWN}:$(uname -n):$(basename $PWD)% "
55
56     # We use read -r to get close to a shell experience
57     while read -e -r -p "$prompt" rawline; do
58         line=
59         case "$rawline" in
60         # Don't want to exit-exit, just exit the REPL
61         exit) break;;
62         # We need to handle continuations, and read -r doesn't do
63         # that for us.  Yet we need read -r.
64         #
65         # We also use case/esac to compare lines read to "*\\"
66         # because [ "$line" = *\\ ] and variants of that don't work.
67         *\\) line="$rawline"
68             while read -e -r -p '> ' rawline
69             do
70                 line="$line"$'\n'"$rawline"
71                 case "$rawline" in
72                 # We could check for here documents by matching
73                 # against *<<*, but who cares.
74                 *\\) continue;;
75                 *) break;;
76                 esac
77             done
78             ;;
79         *) line=$rawline
80         esac
81
82         case "$line" in
83         *\\) break;;
84         esac
85
86         # Finally!  Time to eval.
87         eval "$line"
88     done
89
90     echo $'\n\tExiting interactive shell...\n'
91     return 0
92 }
93
94 # lassert - Lustre test framework assert
95 #
96 # Arguments: failure code, failure message, expression/statement
97 #
98 # lassert evaluates the expression given, and, if false, calls
99 # error() to trigger test failure.  If REPL_ON_LASSERT is true then
100 # lassert will call lrepl() to give the user an interactive shell.
101 # If the REPL sets retcode=0 then the assertion failure will be
102 # ignored.
103 lassert() {
104     local retcode=$1
105     local msg=$2
106     shift 2
107
108     echo "checking $* ($(eval echo \""$*"\"))..."
109     eval "$@" && return 0;
110
111     if ${REPL_ON_LASSERT:-false}; then
112         echo "Assertion $retcode failed: $* (expanded: $(eval echo \""$*"\"))
113 $msg"
114         lrepl
115     fi
116
117     error "Assertion $retcode failed: $* (expanded: $(eval echo \""$*"\"))
118 $msg"
119     return $retcode
120 }
121
122 # setmodopts- set module options for subsequent calls to load_modules
123 #
124 # Usage: setmodopts module_name new_value [var_in_which_to_save_old_value]
125 #        setmodopts -a module_name new_value [var_in_which_to_save_old_value]
126 #
127 # In the second usage the new value is appended to the old.
128 setmodopts() {
129         local _append=false
130
131         if [ "$1" = -a ]; then
132             _append=true
133             shift
134         fi
135
136         local _var=MODOPTS_$1
137         local _newvalue=$2
138         local _savevar=$3
139         local _oldvalue
140
141         # Dynamic naming of variables is a pain in bash.  In ksh93 we could
142         # write "nameref opts_var=${modname}_MODOPTS" then assign directly
143         # to opts_var.  Associative arrays would also help, alternatively.
144         # Alas, we're stuck with eval until all distros move to a more recent
145         # version of bash.  Fortunately we don't need to eval unset and export.
146
147         if [ -z "$_newvalue" ]; then
148             unset $_var
149             return 0
150         fi
151
152         _oldvalue=${!var}
153         $_append && _newvalue="$_oldvalue $_newvalue"
154         export $_var="$_newvalue"
155         echo setmodopts: ${_var}=${_newvalue}
156
157         [ -n "$_savevar" ] && eval $_savevar=\""$_oldvalue"\"
158 }
159
160 echoerr () { echo "$@" 1>&2 ; }
161
162 signaled() {
163     echoerr "$(date +'%F %H:%M:%S'): client load was signaled to terminate"
164
165     local PGID=$(ps -eo "%c %p %r" | awk "/ $PPID / {print \$3}")
166     kill -TERM -$PGID
167     sleep 5
168     kill -KILL -$PGID
169 }
170
171 mpi_run () {
172     local mpirun="$MPIRUN $MPIRUN_OPTIONS"
173     local command="$mpirun $@"
174     local mpilog=$TMP/mpi.log
175     local rc
176
177     if [ -n "$MPI_USER" -a "$MPI_USER" != root -a -n "$mpirun" ]; then
178         echo "+ chmod 0777 $MOUNT"
179         chmod 0777 $MOUNT
180         command="su $MPI_USER sh -c \"$command \""
181     fi
182
183     ls -ald $MOUNT
184     echo "+ $command"
185     eval $command 2>&1 | tee $mpilog || true
186
187     rc=${PIPESTATUS[0]}
188     if [ $rc -eq 0 ] && grep -q "p4_error:" $mpilog ; then
189        rc=1
190     fi
191     return $rc
192 }
193
194 nids_list () {
195         local list
196         local escape="$2"
197         for i in ${1//,/ }; do
198                 if [ "$list" = "" ]; then
199                         list="$i@$NETTYPE"
200                 else
201                         list="$list$escape $i@$NETTYPE"
202                 fi
203         done
204         echo $list
205 }
206
207 # FIXME: all setup/cleanup can be done without rpc.sh
208 lst_end_session () {
209     local verbose=false
210     [ x$1 = x--verbose ] && verbose=true
211
212     export LST_SESSION=`$LST show_session 2>/dev/null | awk -F " " '{print $5}'`
213     [ "$LST_SESSION" == "" ] && return
214
215         $LST stop b
216     if $verbose; then
217         $LST show_error c s
218     fi
219     $LST end_session
220 }
221
222 lst_session_cleanup_all () {
223     local list=$(comma_list $(nodes_list))
224     do_rpc_nodes $list lst_end_session
225 }
226
227 lst_cleanup () {
228     lsmod | grep -q lnet_selftest && \
229         rmmod lnet_selftest > /dev/null 2>&1 || true
230 }
231
232 lst_cleanup_all () {
233    local list=$(comma_list $(nodes_list))
234
235    # lst end_session needs to be executed only locally
236    # i.e. on node where lst new_session was called
237    lst_end_session --verbose
238    do_rpc_nodes $list lst_cleanup
239 }
240
241 lst_setup () {
242     load_module lnet_selftest
243 }
244
245 lst_setup_all () {
246     local list=$(comma_list $(nodes_list))
247     do_rpc_nodes $list lst_setup
248 }
249
250 ###
251 # short_hostname
252 #
253 # Passed a single argument, strips everything off following
254 # and includes the first period.
255 # client-20.lab.whamcloud.com becomes client-20
256 short_hostname() {
257   echo $(sed 's/\..*//' <<< $1)
258 }
259
260 ###
261 # short_nodename
262 #
263 # Find remote nodename, stripped of any domain, etc.
264 # 'hostname -s' is easy, but not implemented on all systems
265 short_nodename() {
266         local rname=$(do_node $1 "uname -n" || echo -1)
267         if [[ "$rname" = "-1" ]]; then
268                 rname=$1
269         fi
270         echo $(short_hostname $rname)
271 }
272
273 print_opts () {
274     local var
275
276     echo OPTIONS:
277
278     for i in $@; do
279         var=$i
280         echo "${var}=${!var}"
281     done
282     [ -e $MACHINEFILE ] && cat $MACHINEFILE
283 }
284
285 run_compilebench() {
286         local dir=${1:-$DIR}
287         local cbench_DIR=${cbench_DIR:-""}
288         local cbench_IDIRS=${cbench_IDIRS:-2}
289         local cbench_RUNS=${cbench_RUNS:-2}
290
291         print_opts cbench_DIR cbench_IDIRS cbench_RUNS
292
293         [ x$cbench_DIR = x ] &&
294                 { skip_env "compilebench not found" && return; }
295
296         [ -e $cbench_DIR/compilebench ] ||
297                 { skip_env "No compilebench build" && return; }
298
299         # Space estimation:
300         # compile dir kernel-0  ~1GB
301         # required space        ~1GB * cbench_IDIRS
302         local space=$(df -P $dir | tail -n 1 | awk '{ print $4 }')
303         if [[ $space -le $((1024 * 1024 * cbench_IDIRS)) ]]; then
304                 cbench_IDIRS=$((space / 1024 / 1024))
305                 [[ $cbench_IDIRS -eq 0 ]] &&
306                         skip_env "Need free space at least 1GB, have $space" &&
307                         return
308
309                 echo "reducing initial dirs to $cbench_IDIRS"
310         fi
311         echo "free space = $space KB"
312
313         # FIXME:
314         # t-f _base needs to be modifyed to set properly tdir
315         # for new "test_foo" functions names
316         # local testdir=$DIR/$tdir
317         local testdir=$dir/d0.compilebench.$$
318         mkdir -p $testdir
319
320     local savePWD=$PWD
321     cd $cbench_DIR
322     local cmd="./compilebench -D $testdir -i $cbench_IDIRS \
323         -r $cbench_RUNS --makej"
324
325     log "$cmd"
326
327     local rc=0
328     eval $cmd
329     rc=$?
330
331     cd $savePWD
332     [ $rc = 0 ] || error "compilebench failed: $rc"
333     rm -rf $testdir
334 }
335
336 run_metabench() {
337         local dir=${1:-$DIR}
338         local mntpt=${2:-$MOUNT}
339         METABENCH=${METABENCH:-$(which metabench 2> /dev/null || true)}
340         mbench_NFILES=${mbench_NFILES:-30400}
341         # threads per client
342         mbench_THREADS=${mbench_THREADS:-4}
343         mbench_OPTIONS=${mbench_OPTIONS:-}
344         mbench_CLEANUP=${mbench_CLEANUP:-true}
345
346         [ x$METABENCH = x ] &&
347                 { skip_env "metabench not found" && return; }
348
349         print_opts METABENCH clients mbench_NFILES mbench_THREADS
350
351         local testdir=$dir/d0.metabench
352         mkdir -p $testdir
353         # mpi_run uses mpiuser
354         chmod 0777 $testdir
355
356         # -C             Run the file creation tests. Creates zero byte files.
357         # -S             Run the file stat tests.
358         # -c nfile       Number of files to be used in each test.
359         # -k             Cleanup files when finished.
360         local cmd="$METABENCH -w $testdir -c $mbench_NFILES -C -S $mbench_OPTIONS"
361         echo "+ $cmd"
362
363         # find out if we need to use srun by checking $SRUN_PARTITION
364         if [ "$SRUN_PARTITION" ]; then
365                 $SRUN $SRUN_OPTIONS -D $testdir -w $clients -N $num_clients \
366                         -n $((num_clients * mbench_THREADS)) \
367                         -p $SRUN_PARTITION -- $cmd
368         else
369                 mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
370                         -np $((num_clients * $mbench_THREADS)) $cmd
371         fi
372
373         local rc=$?
374         if [ $rc != 0 ] ; then
375                 error "metabench failed! $rc"
376         fi
377
378         if $mbench_CLEANUP; then
379                 rm -rf $testdir
380         else
381                 mv $dir/d0.metabench $mntpt/_xxx.$(date +%s).d0.metabench
382         fi
383 }
384
385 run_simul() {
386
387     SIMUL=${SIMUL:=$(which simul 2> /dev/null || true)}
388     # threads per client
389     simul_THREADS=${simul_THREADS:-2}
390     simul_REP=${simul_REP:-20}
391
392     if [ "$NFSCLIENT" ]; then
393         skip "skipped for NFSCLIENT mode"
394         return
395     fi
396
397     [ x$SIMUL = x ] &&
398         { skip_env "simul not found" && return; }
399
400     # FIXME
401     # Need space estimation here.
402
403     print_opts SIMUL clients simul_REP simul_THREADS
404
405     local testdir=$DIR/d0.simul
406     mkdir -p $testdir
407     # mpi_run uses mpiuser
408     chmod 0777 $testdir
409
410     # -n # : repeat each test # times
411     # -N # : repeat the entire set of tests # times
412
413     local cmd="$SIMUL -d $testdir -n $simul_REP -N $simul_REP"
414
415         echo "+ $cmd"
416         # find out if we need to use srun by checking $SRUN_PARTITION
417         if [ "$SRUN_PARTITION" ]; then
418                 $SRUN $SRUN_OPTIONS -D $testdir -w $clients -N $num_clients \
419                         -n $((num_clients * simul_THREADS)) -p $SRUN_PARTITION \
420                         -- $cmd
421         else
422                 mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
423                         -np $((num_clients * simul_THREADS)) $cmd
424         fi
425
426     local rc=$?
427     if [ $rc != 0 ] ; then
428         error "simul failed! $rc"
429     fi
430     rm -rf $testdir
431 }
432
433 run_mdtest() {
434
435     MDTEST=${MDTEST:=$(which mdtest 2> /dev/null || true)}
436     # threads per client
437     mdtest_THREADS=${mdtest_THREADS:-2}
438     mdtest_nFiles=${mdtest_nFiles:-"100000"}
439     # We devide the files by number of core
440     mdtest_nFiles=$((mdtest_nFiles/mdtest_THREADS/num_clients))
441     mdtest_iteration=${mdtest_iteration:-1}
442         local mdtest_custom_params=${mdtest_custom_params:-""}
443
444     local type=${1:-"ssf"}
445
446     if [ "$NFSCLIENT" ]; then
447         skip "skipped for NFSCLIENT mode"
448         return
449     fi
450
451     [ x$MDTEST = x ] &&
452         { skip_env "mdtest not found" && return; }
453
454     # FIXME
455     # Need space estimation here.
456
457     print_opts MDTEST mdtest_iteration mdtest_THREADS mdtest_nFiles
458
459     local testdir=$DIR/d0.mdtest
460     mkdir -p $testdir
461     # mpi_run uses mpiuser
462     chmod 0777 $testdir
463
464     # -i # : repeat each test # times
465     # -d   : test dir
466     # -n # : number of file/dir to create/stat/remove
467     # -u   : each process create/stat/remove individually
468
469         local cmd="$MDTEST -d $testdir -i $mdtest_iteration \
470                 -n $mdtest_nFiles $mdtest_custom_params"
471
472         [ $type = "fpp" ] && cmd="$cmd -u"
473
474         echo "+ $cmd"
475         # find out if we need to use srun by checking $SRUN_PARTITION
476         if [ "$SRUN_PARTITION" ]; then
477                 $SRUN $SRUN_OPTIONS -D $testdir -w $clients -N $num_clients \
478                         -n $((num_clients * mdtest_THREADS)) \
479                         -p $SRUN_PARTITION -- $cmd
480         else
481                 mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
482                         -np $((num_clients * mdtest_THREADS)) $cmd
483         fi
484
485     local rc=$?
486     if [ $rc != 0 ] ; then
487         error "mdtest failed! $rc"
488     fi
489     rm -rf $testdir
490 }
491
492 run_connectathon() {
493         local dir=${1:-$DIR}
494         cnt_DIR=${cnt_DIR:-""}
495         cnt_NRUN=${cnt_NRUN:-10}
496
497         print_opts cnt_DIR cnt_NRUN
498
499         [ x$cnt_DIR = x ] &&
500                 { skip_env "connectathon dir not found" && return; }
501
502         [ -e $cnt_DIR/runtests ] ||
503                 { skip_env "No connectathon runtests found" && return; }
504
505         # Space estimation:
506         # "special" tests create a 30 MB file + misc. small files
507         # required space ~40 MB
508         local space=$(df -P $dir | tail -n 1 | awk '{ print $4 }')
509         if [[ $space -le $((1024 * 40)) ]]; then
510                 skip_env "Need free space at least 40MB, have $space KB" &&
511                 return
512         fi
513         echo "free space = $space KB"
514
515         local testdir=$dir/d0.connectathon
516         mkdir -p $testdir
517
518         local savePWD=$PWD
519         cd $cnt_DIR
520
521         #
522         # To run connectathon:
523         # runtests [-a|-b|-g|-s|-l] [-f|-n|-t] [-N numpasses] [test-directory]
524         #
525         # One of the following test types
526         #    -b  basic
527         #    -g  general
528         #    -s  special
529         #    -l  lock
530         #    -a  all of the above
531         #
532         # -f  a quick functional test
533         # -n  suppress directory operations (mkdir and rmdir)
534         # -t  run with time statistics (default for basic tests)
535         #
536         # -N numpasses - specifies the number of times to run
537         #                the tests. Optional.
538
539         tests="-b -g -s"
540         # Include lock tests unless we're running on nfsv4
541         local fstype=$(df -TP $testdir | awk 'NR==2  {print $2}')
542         echo "$testdir: $fstype"
543         if [[ $fstype != "nfs4" ]]; then
544                 tests="$tests -l"
545         fi
546         echo "tests: $tests"
547         for test in $tests; do
548                 local cmd="./runtests -N $cnt_NRUN $test -f $testdir"
549                 local rc=0
550
551                 log "$cmd"
552                 eval $cmd
553                 rc=$?
554                 [ $rc = 0 ] || error "connectathon failed: $rc"
555         done
556
557         cd $savePWD
558         rm -rf $testdir
559 }
560
561 run_ior() {
562         local type=${1:="ssf"}
563         local dir=${2:-$DIR}
564         local testdir=$dir/d0.ior.$type
565         local nfs_srvmntpt=$3
566
567         if [ "$NFSCLIENT" ]; then
568                 [[ -n $nfs_srvmntpt ]] ||
569                         { error "NFSCLIENT mode, but nfs exported dir"\
570                                 "is not set!" && return 1; }
571         fi
572
573         IOR=${IOR:-$(which IOR 2> /dev/null || true)}
574         [ x$IOR = x ] &&
575                 { skip_env "IOR not found" && return; }
576
577         # threads per client
578         ior_THREADS=${ior_THREADS:-2}
579         ior_iteration=${ior_iteration:-1}
580         ior_blockSize=${ior_blockSize:-6}
581         ior_blockUnit=${ior_blockUnit:-M}   # K, M, G
582         ior_xferSize=${ior_xferSize:-1M}
583         ior_type=${ior_type:-POSIX}
584         ior_DURATION=${ior_DURATION:-30}        # minutes
585         local multiplier=1
586         case ${ior_blockUnit} in
587                 [G])
588                         multiplier=$((1024 * 1024 * 1024))
589                         ;;
590                 [M])
591                         multiplier=$((1024 * 1024))
592                         ;;
593                 [K])
594                         multiplier=1024
595                         ;;
596                 *)      error "Incorrect block unit should be one of [KMG]"
597                         ;;
598         esac
599
600         # calculate the space in bytes
601         local space=$(df -B 1 -P $dir | tail -n 1 | awk '{ print $4 }')
602         local total_threads=$((num_clients * ior_THREADS))
603         echo "+ $ior_blockSize * $multiplier * $total_threads "
604         if [ $((space / 2)) -le \
605              $((ior_blockSize * multiplier * total_threads)) ]; then
606                 ior_blockSize=$((space / 2 / multiplier / total_threads))
607                 [ $ior_blockSize -eq 0 ] && \
608                 skip_env "Need free space more than $((2 * total_threads)) \
609                          ${ior_blockUnit}: have $((space / multiplier))" &&
610                          return
611
612                 echo "(reduced blockSize to $ior_blockSize \
613                      ${ior_blockUnit} bytes)"
614         fi
615
616         print_opts IOR ior_THREADS ior_DURATION MACHINEFILE
617
618         mkdir -p $testdir
619         # mpi_run uses mpiuser
620         chmod 0777 $testdir
621         if [ -z "$NFSCLIENT" ]; then
622                 $LFS setstripe $testdir -c -1 ||
623                         { error "setstripe failed" && return 2; }
624         fi
625
626         #
627         # -b N  blockSize --
628         #       contiguous bytes to write per task (e.g.: 8, 4K, 2M, 1G)"
629         # -o S  testFileName
630         # -t N  transferSize -- size of transfer in bytes (e.g.: 8, 4K, 2M, 1G)"
631         # -w    writeFile -- write file"
632         # -r    readFile -- read existing file"
633         # -W    checkWrite -- check read after write"
634         # -C    reorderTasks -- changes task ordering to n+1 ordering for readback
635         # -T    maxTimeDuration -- max time in minutes to run tests"
636         # -k    keepFile -- keep testFile(s) on program exit
637
638         local cmd="$IOR -a $ior_type -b ${ior_blockSize}${ior_blockUnit} \
639                 -o $testdir/iorData -t $ior_xferSize -v -C -w -r -W \
640                 -i $ior_iteration -T $ior_DURATION -k"
641
642         [ $type = "fpp" ] && cmd="$cmd -F"
643
644         echo "+ $cmd"
645         # find out if we need to use srun by checking $SRUN_PARTITION
646         if [ "$SRUN_PARTITION" ]; then
647                 $SRUN $SRUN_OPTIONS -D $testdir -w $clients -N $num_clients \
648                         -n $((num_clients * ior_THREADS)) -p $SRUN_PARTITION \
649                         -- $cmd
650         else
651                 mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
652                         -np $((num_clients * $ior_THREADS)) $cmd
653         fi
654
655     local rc=$?
656     if [ $rc != 0 ] ; then
657         error "ior failed! $rc"
658     fi
659     rm -rf $testdir
660 }
661
662 run_mib() {
663
664     MIB=${MIB:=$(which mib 2> /dev/null || true)}
665     # threads per client
666     mib_THREADS=${mib_THREADS:-2}
667     mib_xferSize=${mib_xferSize:-1m}
668     mib_xferLimit=${mib_xferLimit:-5000}
669     mib_timeLimit=${mib_timeLimit:-300}
670
671     if [ "$NFSCLIENT" ]; then
672         skip "skipped for NFSCLIENT mode"
673         return
674     fi
675
676     [ x$MIB = x ] &&
677         { skip_env "MIB not found" && return; }
678
679     print_opts MIB mib_THREADS mib_xferSize mib_xferLimit mib_timeLimit \
680         MACHINEFILE
681
682     local testdir=$DIR/d0.mib
683     mkdir -p $testdir
684     # mpi_run uses mpiuser
685     chmod 0777 $testdir
686     $LFS setstripe $testdir -c -1 ||
687         { error "setstripe failed" && return 2; }
688     #
689     # -I    Show intermediate values in output
690     # -H    Show headers in output
691     # -L    Do not issue new system calls after this many seconds
692     # -s    Use system calls of this size
693     # -t    test dir
694     # -l    Issue no more than this many system calls
695     local cmd="$MIB -t $testdir -s $mib_xferSize -l $mib_xferLimit \
696         -L $mib_timeLimit -HI -p mib.$(date +%Y%m%d%H%M%S)"
697
698         echo "+ $cmd"
699         # find out if we need to use srun by checking $SRUN_PARTITION
700         if [ "$SRUN_PARTITION" ]; then
701                 $SRUN $SRUN_OPTIONS -D $testdir -w $clients -N $num_clients \
702                         -n $((num_clients * mib_THREADS)) -p $SRUN_PARTITION \
703                         -- $cmd
704         else
705                 mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
706                         -np $((num_clients * mib_THREADS)) $cmd
707         fi
708
709     local rc=$?
710     if [ $rc != 0 ] ; then
711         error "mib failed! $rc"
712     fi
713     rm -rf $testdir
714 }
715
716 run_cascading_rw() {
717
718     CASC_RW=${CASC_RW:-$(which cascading_rw 2> /dev/null || true)}
719     # threads per client
720     casc_THREADS=${casc_THREADS:-2}
721     casc_REP=${casc_REP:-300}
722
723     if [ "$NFSCLIENT" ]; then
724         skip "skipped for NFSCLIENT mode"
725         return
726     fi
727
728     [ x$CASC_RW = x ] &&
729         { skip_env "cascading_rw not found" && return; }
730
731     # FIXME
732     # Need space estimation here.
733
734     print_opts CASC_RW clients casc_THREADS casc_REP MACHINEFILE
735
736     local testdir=$DIR/d0.cascading_rw
737     mkdir -p $testdir
738     # mpi_run uses mpiuser
739     chmod 0777 $testdir
740
741     # -g: debug mode
742     # -n: repeat test # times
743
744     local cmd="$CASC_RW -g -d $testdir -n $casc_REP"
745
746         echo "+ $cmd"
747         mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
748                 -np $((num_clients * $casc_THREADS)) $cmd
749
750     local rc=$?
751     if [ $rc != 0 ] ; then
752         error "cascading_rw failed! $rc"
753     fi
754     rm -rf $testdir
755 }
756
757 run_write_append_truncate() {
758
759     # threads per client
760     write_THREADS=${write_THREADS:-8}
761     write_REP=${write_REP:-10000}
762
763     if [ "$NFSCLIENT" ]; then
764         skip "skipped for NFSCLIENT mode"
765         return
766     fi
767
768     # location is lustre/tests dir
769     if ! which write_append_truncate > /dev/null 2>&1 ; then
770         skip_env "write_append_truncate not found"
771         return
772     fi
773
774     # FIXME
775     # Need space estimation here.
776
777     local testdir=$DIR/d0.write_append_truncate
778     local file=$testdir/f0.wat
779
780     print_opts clients write_REP write_THREADS MACHINEFILE
781
782     mkdir -p $testdir
783     # mpi_run uses mpiuser
784     chmod 0777 $testdir
785
786     local cmd="write_append_truncate -n $write_REP $file"
787
788         echo "+ $cmd"
789         mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
790                 -np $((num_clients * $write_THREADS)) $cmd
791
792     local rc=$?
793     if [ $rc != 0 ] ; then
794         error "write_append_truncate failed! $rc"
795         return $rc
796     fi
797     rm -rf $testdir
798 }
799
800 run_write_disjoint() {
801         if [ "$NFSCLIENT" ]; then
802                 skip "skipped for NFSCLIENT mode"
803                 return
804         fi
805
806         WRITE_DISJOINT=${WRITE_DISJOINT:-$(which write_disjoint 2> /dev/null ||
807                                            true)}
808
809         [ x$WRITE_DISJOINT = x ] &&
810                 { skip_env "write_disjoint not found" && return; }
811
812         # threads per client
813         wdisjoint_THREADS=${wdisjoint_THREADS:-4}
814         wdisjoint_REP=${wdisjoint_REP:-10000}
815         chunk_size_limit=$1
816
817     # FIXME
818     # Need space estimation here.
819
820     print_opts WRITE_DISJOINT clients wdisjoint_THREADS wdisjoint_REP \
821         MACHINEFILE
822     local testdir=$DIR/d0.write_disjoint
823     mkdir -p $testdir
824     # mpi_run uses mpiuser
825     chmod 0777 $testdir
826
827         local cmd="$WRITE_DISJOINT -f $testdir/file -n $wdisjoint_REP -m \
828                         $chunk_size_limit"
829
830         echo "+ $cmd"
831         mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
832                 -np $((num_clients * $wdisjoint_THREADS)) $cmd
833
834     local rc=$?
835     if [ $rc != 0 ] ; then
836         error "write_disjoint failed! $rc"
837     fi
838     rm -rf $testdir
839 }
840
841 run_parallel_grouplock() {
842
843     PARALLEL_GROUPLOCK=${PARALLEL_GROUPLOCK:-$(which parallel_grouplock \
844         2> /dev/null || true)}
845     parallel_grouplock_MINTASKS=${parallel_grouplock_MINTASKS:-5}
846
847     if [ "$NFSCLIENT" ]; then
848         skip "skipped for NFSCLIENT mode"
849         return
850     fi
851
852     [ x$PARALLEL_GROUPLOCK = x ] &&
853         { skip "PARALLEL_GROUPLOCK not found" && return; }
854
855     print_opts clients parallel_grouplock_MINTASKS MACHINEFILE
856
857     local testdir=$DIR/d0.parallel_grouplock
858     mkdir -p $testdir
859     # mpi_run uses mpiuser
860     chmod 0777 $testdir
861
862     local cmd
863     local status=0
864     local subtest
865         for i in $(seq 12); do
866                 subtest="-t $i"
867                 local cmd="$PARALLEL_GROUPLOCK -g -v -d $testdir $subtest"
868                 echo "+ $cmd"
869
870                 mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
871                         -np $parallel_grouplock_MINTASKS $cmd
872                 local rc=$?
873                 if [ $rc != 0 ] ; then
874                         error_noexit "parallel_grouplock subtests $subtest " \
875                                      "failed! $rc"
876                 else
877                         echo "parallel_grouplock subtests $subtest PASS"
878                 fi
879                 let status=$((status + rc))
880                 # clear debug to collect one log per one test
881                 do_nodes $(comma_list $(nodes_list)) lctl clear
882         done
883         [ $status -eq 0 ] || error "parallel_grouplock status: $status"
884         rm -rf $testdir
885 }
886
887 cleanup_statahead () {
888     trap 0
889
890     local clients=$1
891     local mntpt_root=$2
892     local num_mntpts=$3
893
894     for i in $(seq 0 $num_mntpts);do
895         zconf_umount_clients $clients ${mntpt_root}$i ||
896             error_exit "Failed to umount lustre on ${mntpt_root}$i"
897     done
898 }
899
900 run_statahead () {
901
902     statahead_NUMMNTPTS=${statahead_NUMMNTPTS:-5}
903     statahead_NUMFILES=${statahead_NUMFILES:-500000}
904
905     if [[ -n $NFSCLIENT ]]; then
906         skip "Statahead testing is not supported on NFS clients."
907         return 0
908     fi
909
910     [ x$MDSRATE = x ] &&
911         { skip_env "mdsrate not found" && return; }
912
913     print_opts MDSRATE clients statahead_NUMMNTPTS statahead_NUMFILES
914
915     # create large dir
916
917     # do not use default "d[0-9]*" dir name
918     # to avoid of rm $statahead_NUMFILES (500k) files in t-f cleanup
919     local dir=dstatahead
920     local testdir=$DIR/$dir
921
922     # cleanup only if dir exists
923     # cleanup only $statahead_NUMFILES number of files
924     # ignore the other files created by someone else
925     [ -d $testdir ] &&
926         mdsrate_cleanup $((num_clients * 32)) $MACHINEFILE \
927             $statahead_NUMFILES $testdir 'f%%d' --ignore
928
929     mkdir -p $testdir
930     # mpi_run uses mpiuser
931     chmod 0777 $testdir
932
933     local num_files=$statahead_NUMFILES
934
935     local IFree=$(inodes_available)
936     if [ $IFree -lt $num_files ]; then
937       num_files=$IFree
938     fi
939
940     cancel_lru_locks mdc
941
942     local cmd1="${MDSRATE} ${MDSRATE_DEBUG} --mknod --dir $testdir"
943     local cmd2="--nfiles $num_files --filefmt 'f%%d'"
944     local cmd="$cmd1 $cmd2"
945     echo "+ $cmd"
946
947         mpi_run ${MACHINEFILE_OPTION} ${MACHINEFILE} \
948                 -np $((num_clients * 32)) $cmd
949
950     local rc=$?
951     if [ $rc != 0 ] ; then
952         error "mdsrate failed to create $rc"
953         return $rc
954     fi
955
956     local num_mntpts=$statahead_NUMMNTPTS
957     local mntpt_root=$TMP/mntpt/lustre
958     local mntopts=$MNTOPTSTATAHEAD
959
960     echo "Mounting $num_mntpts lustre clients starts on $clients"
961     trap "cleanup_statahead $clients $mntpt_root $num_mntpts" EXIT ERR
962     for i in $(seq 0 $num_mntpts); do
963         zconf_mount_clients $clients ${mntpt_root}$i "$mntopts" ||
964             error_exit "Failed to mount lustre on ${mntpt_root}$i on $clients"
965     done
966
967     do_rpc_nodes $clients cancel_lru_locks mdc
968
969     do_rpc_nodes $clients do_ls $mntpt_root $num_mntpts $dir
970
971     mdsrate_cleanup $((num_clients * 32)) $MACHINEFILE \
972         $num_files $testdir 'f%%d' --ignore
973
974     # use rm instead of rmdir because of
975     # testdir could contain the files created by someone else,
976     # or by previous run where is num_files prev > num_files current
977     rm -rf $testdir
978     cleanup_statahead $clients $mntpt_root $num_mntpts
979 }
980
981 cleanup_rr_alloc () {
982         trap 0
983         local clients="$1"
984         local mntpt_root="$2"
985         local rr_alloc_MNTPTS="$3"
986         local mntpt_dir=$(dirname ${mntpt_root})
987
988         for i in $(seq 0 $((rr_alloc_MNTPTS - 1))); do
989                 zconf_umount_clients $clients ${mntpt_root}$i ||
990                 error_exit "Failed to umount lustre on ${mntpt_root}$i"
991         done
992         do_nodes $clients "rm -rf $mntpt_dir"
993 }
994
995 run_rr_alloc() {
996         remote_mds_nodsh && skip "remote MDS with nodsh" && return
997         echo "===Test gives more reproduction percentage if number of "\
998                 "client and ost are more. Test with 44 or more clients "\
999                 "and 73 or more OSTs gives 100% reproduction rate=="
1000
1001         RR_ALLOC=${RR_ALLOC:-$(which rr_alloc 2> /dev/null || true)}
1002         [ x$RR_ALLOC = x ] && { skip_env "rr_alloc not found" && return; }
1003         declare -a diff_max_min_arr
1004         # foeo = file on each ost. calc = calculated.
1005         local ost_idx
1006         local foeo_calc
1007         local qos_prec_objs="${TMP}/qos_and_precreated_objects"
1008         local rr_alloc_NFILES=${rr_alloc_NFILES:-555}
1009         local rr_alloc_MNTPTS=${rr_alloc_MNTPTS:-11}
1010         local total_MNTPTS=$((rr_alloc_MNTPTS * num_clients))
1011         local mntpt_root="${TMP}/rr_alloc_mntpt/lustre"
1012         if [ $MDSCOUNT -lt 2 ]; then
1013                 [ -e $DIR/$tdir ] || mkdir -p $DIR/$tdir
1014         else
1015                 [ -e $DIR/$tdir ] || $LFS mkdir -i 0 $DIR/$tdir
1016         fi
1017         chmod 0777 $DIR/$tdir
1018         $SETSTRIPE -c 1 /$DIR/$tdir
1019
1020         trap "cleanup_rr_alloc $clients $mntpt_root $rr_alloc_MNTPTS" EXIT ERR
1021         for i in $(seq 0 $((rr_alloc_MNTPTS - 1))); do
1022                 zconf_mount_clients $clients ${mntpt_root}$i $MOUNT_OPTS ||
1023                 error_exit "Failed to mount lustre on ${mntpt_root}$i $clients"
1024         done
1025
1026         local cmd="$RR_ALLOC $mntpt_root/$tdir/ash $rr_alloc_NFILES \
1027                 $num_clients"
1028
1029         # Save mdt values, set threshold to 100% i.e always Round Robin,
1030         # restore the saved values again after creating files...
1031         save_lustre_params mds1 \
1032                 "lov.lustre-MDT0000*.qos_threshold_rr" > $qos_prec_objs
1033         save_lustre_params mds1 \
1034                 "osp.lustre-OST*-osc-MDT0000.create_count" >> $qos_prec_objs
1035
1036         local old_create_count=$(grep -e "create_count" $qos_prec_objs |
1037                 cut -d'=' -f 2 | sort -nr | head -n1)
1038
1039         # Make sure that every osp has enough precreated objects for the file
1040         # creation app
1041
1042         # create_count is always set to the power of 2 only, so if the files
1043         # per OST are not multiple of that then it will be set to nearest
1044         # lower power of 2. So set 'create_count' to the upper power of 2.
1045
1046         foeo_calc=$((rr_alloc_NFILES * total_MNTPTS / OSTCOUNT))
1047         local create_count=$((2 * foeo_calc))
1048         do_facet mds1 "$LCTL set_param -n \
1049                 lov.lustre-MDT0000*.qos_threshold_rr 100 \
1050                 osp.lustre-OST*-osc-MDT0000.create_count $create_count" ||
1051                 error "failed while setting qos_threshold_rr & creat_count"
1052
1053         # Create few temporary files in order to increase the precreated objects
1054         # to a desired value, before starting 'rr_alloc' app. Due to default
1055         # value 32 of precreation count (OST_MIN_PRECREATE=32), precreated
1056         # objects available are 32 initially, these gets exhausted very soon,
1057         # which causes skip of some osps when very large number of files
1058         # is created per OSTs.
1059         createmany -o $DIR/$tdir/foo- $(((old_create_count + 1) * OSTCOUNT)) \
1060                 > /dev/null
1061         rm -f /$DIR/$tdir/foo*
1062
1063         # Check for enough precreated objects... We should not
1064         # fail here because code(osp_precreate.c) also takes care of it.
1065         # So we have good chances of passing test even if this check fails.
1066         local mdt_idx=0
1067         for ost_idx in $(seq 0 $((OSTCOUNT - 1))); do
1068                 [[ $(precreated_ost_obj_count $mdt_idx $ost_idx) -ge \
1069                         $foeo_calc ]] || echo "Warning: test may fail because" \
1070                         "of lack of precreated objects on OST${ost_idx}"
1071         done
1072
1073         if [[ $total_MNTPTS -ne 0 ]]; then
1074                 # Now start the actual file creation app.
1075                 mpi_run "-np $total_MNTPTS" $cmd || return
1076         else
1077                 error "No mount point"
1078         fi
1079
1080         restore_lustre_params < $qos_prec_objs
1081         rm -f $qos_prec_objs
1082
1083         diff_max_min_arr=($($GETSTRIPE -r $DIR/$tdir/ |
1084                 grep "lmm_stripe_offset:" | awk '{print $2}' | sort -n |
1085                 uniq -c | awk 'NR==1 {min=max=$1} \
1086                 { $1<min ? min=$1 : min; $1>max ? max=$1 : max} \
1087                 END {print max-min, max, min}'))
1088
1089         rm -rf $DIR/$tdir
1090
1091         # In-case of fairly large number of file creation using RR (round-robin)
1092         # there can be two cases in which deviation will occur than the regular
1093         # RR algo behaviour-
1094         # 1- When rr_alloc does not start right with 'lqr_start_count' reseeded,
1095         # 2- When rr_alloc does not finish with 'lqr_start_count == 0'.
1096         # So the difference of files b/w any 2 OST should not be more than 2.
1097         [[ ${diff_max_min_arr[0]} -le 2 ]] ||
1098                 error "Uneven distribution detected: difference between" \
1099                 "maximum files per OST (${diff_max_min_arr[1]}) and" \
1100                 "minimum files per OST (${diff_max_min_arr[2]}) must not be" \
1101                 "greater than 2"
1102 }
1103
1104 run_fs_test() {
1105         # fs_test.x is the default name for exe
1106         FS_TEST=${FS_TEST:=$(which fs_test.x 2> /dev/null || true)}
1107
1108         local clients=${CLIENTS:-$(hostname)}
1109         local testdir=$DIR/d0.fs_test
1110         local file=${testdir}/fs_test
1111         fs_test_threads=${fs_test_threads:-2}
1112         fs_test_type=${fs_test_type:-1}
1113         fs_test_nobj=${fs_test_nobj:-10}
1114         fs_test_check=${fs_test_check:-3}
1115         fs_test_strided=${fs_test_strided:-1}
1116         fs_test_touch=${fs_test_touch:-3}
1117         fs_test_supersize=${fs_test_supersize:-1}
1118         fs_test_op=${fs_test_op:-write}
1119         fs_test_barriers=${fs_test_barriers:-bopen,bwrite,bclose}
1120         fs_test_io=${fs_test_io:-mpi}
1121         fs_test_objsize=${fs_test_objsize:-100}
1122         fs_test_objunit=${fs_test_objunit:-1048576} # 1 mb
1123         fs_test_ndirs=${fs_test_ndirs:-80000}
1124
1125         [ x$FS_TEST = x ] &&
1126                 { skip "FS_TEST not found" && return; }
1127
1128         # Space estimation  in bytes
1129         local space=$(df -B 1 -P $dir | tail -n 1 | awk '{ print $4 }')
1130         local total_threads=$((num_clients * fs_test_threads))
1131         echo "+ $fs_test_objsize * $fs_test_objunit * $total_threads "
1132         if [ $((space / 2)) -le \
1133                 $((fs_test_objsize * fs_test_objunit * total_threads)) ]; then
1134                         fs_test_objsize=$((space / 2 / fs_test_objunit / \
1135                                 total_threads))
1136                         [ $fs_test_objsize -eq 0 ] && \
1137                         skip_env "Need free space more than \
1138                                 $((2 * total_threads * fs_test_objunit)) \
1139                                 : have $((space / fs_test_objunit))" &&
1140                                 return
1141
1142                         echo "(reduced objsize to \
1143                                 $((fs_test_objsize * fs_test_objunit)) bytes)"
1144         fi
1145
1146         print_opts FS_TEST clients fs_test_threads fs_test_objsize MACHINEFILE
1147
1148         mkdir -p $testdir
1149         # mpi_run uses mpiuser
1150         chmod 0777 $testdir
1151
1152         # --nodb          Turn off the database code at runtime
1153         # -g --target     The path to the data file
1154         # -t --type       Whether to do N-N (1) or N-1 (2)
1155         # -n --nobj       The number of objects written/read by each proc
1156         # -z --size       The size of each object
1157         # -d ---num_nn_dirs Number of subdirectories for files
1158         # -C --check      Check every byte using argument 3.
1159         # --collective    Whether to use collective I/O (for N-1, mpi-io only)
1160         # -s --strided    Whether to use a strided pattern (for N-1 only)
1161         # -T --touch      Touch every byte using argument 3
1162         # -o --op         Whether to read only (read) or write only (write)
1163         # -b --barriers   When to barrier.
1164         # -i --io         Use POSIX, MPI, or PLFS IO routines (mpi|posix|plfs)
1165         # -S --supersize  Specify how many objects per superblock
1166
1167         local cmd="$FS_TEST -nodb -g $file -t $fs_test_type -n $fs_test_nobj \
1168                 -z $((fs_test_objsize * fs_test_objunit)) -d $fs_test_ndirs \
1169                 -C $fs_test_check -collective -s $fs_test_strided \
1170                 -T $fs_test_touch -o $fs_test_op -b $fs_test_barriers \
1171                 -i $fs_test_io -S $fs_test_supersize"
1172
1173         echo "+ $cmd"
1174         mpi_run "-np $((num_clients * fs_test_threads))" $cmd
1175
1176         local rc=$?
1177         if [ $rc != 0 ] ; then
1178                 error "fs_test failed! $rc"
1179         fi
1180
1181         rm -rf $testdir
1182 }
1183
1184 ior_mdtest_parallel() {
1185         local rc1=0
1186         local rc2=0
1187         local type=$1
1188
1189         run_ior $type &
1190         local pids=$!
1191
1192         run_mdtest $type || rc2=$?
1193         [[ $rc2 -ne 0 ]] && echo "mdtest failed with error $rc2"
1194
1195         wait $pids || rc1=$?
1196         [[ $rc1 -ne 0 ]] && echo "ior failed with error $rc1"
1197
1198         [[ $rc1 -ne 0 || $rc2 -ne 0 ]] && return 1
1199         return 0
1200 }
1201
1202 run_fio() {
1203         FIO=${FIO:=$(which fio 2> /dev/null || true)}
1204
1205         local clients=${CLIENTS:-$(hostname)}
1206         local fio_jobNum=${fio_jobNum:-4}
1207         local fio_jobFile=${fio_jobFile:-$TMP/fiojobfile.$(date +%s)}
1208         local fio_bs=${fio_bs:-1}
1209         local testdir=$DIR/d0.fio
1210         local file=${testdir}/fio
1211         local runtime=60
1212         local propagate=false
1213
1214         [ "$SLOW" = "no" ] || runtime=600
1215
1216         [ x$FIO = x ] &&
1217                 { skip_env "FIO not found" && return; }
1218
1219         mkdir -p $testdir
1220
1221         # use fio job file if exists,
1222         # create a simple one if missing
1223         if ! [ -f $fio_jobFile ]; then
1224                 cat >> $fio_jobFile <<EOF
1225 [global]
1226 rw=randwrite
1227 size=128m
1228 time_based=1
1229 runtime=$runtime
1230 filename=${file}_\$(hostname)
1231 EOF
1232                 # bs size increased by $i for each job
1233                 for ((i=1; i<=fio_jobNum; i++)); do
1234                         cat >> $fio_jobFile <<EOF
1235
1236 [job$i]
1237 bs=$(( fio_bs * i ))m
1238 EOF
1239                 done
1240                 # job file is created, should be propagated to all clients
1241                 propagate=true
1242         fi
1243
1244
1245         # propagate the job file if not all clients have it yet or
1246         # if the job file was created during the test run
1247         if ! do_nodesv $clients " [ -f $fio_jobFile ] " ||
1248            $propagate; then
1249                 local cfg=$(cat $fio_jobFile)
1250                 do_nodes $clients "echo \\\"$cfg\\\" > ${fio_jobFile}" ||
1251                         error "job file $fio_jobFile is not propagated"
1252                 do_nodesv $clients "cat ${fio_jobFile}"
1253         fi
1254
1255         cmd="$FIO $fio_jobFile"
1256         echo "+ $cmd"
1257
1258         log "clients: $clients $cmd"
1259
1260         local rc=0
1261         do_nodesv $clients "$cmd "
1262         rc=$?
1263
1264         [ $rc = 0 ] || error "fio failed: $rc"
1265         rm -rf $testdir
1266 }
1267
1268 run_xdd() {
1269         XDD=${XDD:=$(which xdd 2> /dev/null || true)}
1270
1271         local clients=${CLIENTS:-$(hostname)}
1272         local testdir=$DIR/d0.xdd
1273         xdd_queuedepth=${xdd_queuedepth:-4}
1274         xdd_blocksize=${xdd_blocksize:-512}
1275         xdd_reqsize=${xdd_reqsize:-128}
1276         xdd_mbytes=${xdd_mbytes:-100}
1277         xdd_passes=${xdd_passes:-40}
1278         xdd_rwratio=${xdd_rwratio:-0}
1279         xdd_ntargets=${xdd_ntargets:-6}
1280         local xdd_custom_params=${xdd_custom_params:-"-dio -stoponerror \
1281                 -maxpri -minall -noproclock -nomemlock"}
1282
1283         [ x$XDD = x ] &&
1284                 { skip "XDD not found" && return; }
1285
1286         print_opts XDD clients xdd_queuedepth xdd_blocksize xdd_reqsize \
1287                 xdd_mbytes xdd_passes xdd_rwratio
1288
1289         mkdir -p $testdir
1290
1291         local files=""
1292         # Target files creates based on the given number of targets
1293         for (( i=0; i < $xdd_ntargets; i++ ))
1294         do
1295                 files+="${testdir}/xdd"$i" "
1296         done
1297
1298         # -targets      specifies the devices or files to perform operation
1299         # -reqsize      number of 'blocks' per operation
1300         # -mbytes       number of 1024*1024-byte blocks to transfer
1301         # -blocksize    size of a single 'block'
1302         # -passes       number of times to read mbytes
1303         # -queuedepth   number of commands to queue on the target
1304         # -rwratio      percentage of read to write operations
1305         # -verbose      will print out statistics on each pass
1306
1307         local cmd="$XDD -targets $xdd_ntargets $files -reqsize $xdd_reqsize \
1308                 -mbytes $xdd_mbytes -blocksize $xdd_blocksize \
1309                 -passes $xdd_passes -queuedepth $xdd_queuedepth \
1310                 -rwratio $xdd_rwratio -verbose $xdd_custom_params"
1311         echo "+ $cmd"
1312
1313         local rc=0
1314         do_nodesv $clients "$cmd "
1315         rc=$?
1316
1317         [ $rc = 0 ] || error "xdd failed: $rc"
1318
1319         rm -rf $testdir
1320 }