]> git.uio.no Git - u/mrichter/AliRoot.git/blob - PWGPP/benchmark/alirelval
Add drawer of empirical
[u/mrichter/AliRoot.git] / PWGPP / benchmark / alirelval
1 #!/bin/bash
2
3 #
4 # alirelval -- by Dario Berzano <dario.berzano@cern.ch>
5 #
6 # Controls the release validation submission by managing the validation virtual
7 # cluster.
8 #
9
10 #
11 # Variables
12 #
13
14 # error codes
15 errCfg=1
16 errMissingCmd=2
17 errEc2Auth=3
18 errInvalidOpt=4
19 errSessionDir=5
20 errCreateKey=6
21 errRunVm=7
22 errLaunchValidation=8
23 errSshNotReady=9
24 errStatusUnavailable=10
25 errPickSession=11
26 errCopyKey=12
27 errAttachScreen=13
28 errRecycleSession=14
29
30 # error codes not treated as errors (100 to 140)
31 errStatusRunning=100
32 errStatusNotRunning=101
33 errStatusDoneOk=102
34 errStatusDoneFail=103
35
36 # thresholds
37 maxVmLaunchAttempts=100
38 maxSshConnectAttempts=400
39 maxVmAddressWait=200
40
41 # working directory prefix
42 sessionPrefix="$HOME/.alice-release-validation"
43
44 # screen name: <screenPrefix>-<sessionTag>
45 screenPrefix='AliRelVal'
46
47 # program name
48 Prog=$(basename "$0")
49
50 #
51 # Functions
52 #
53
54 # Pretty print
55 function pr() {
56   local nl
57   if [ "$1" == '-n' ] ; then
58     nl="-n"
59     shift
60   fi
61   echo $nl -e "\033[1m$@\033[m" >&2
62 }
63
64 # Nice date in UTC
65 function ndate() {
66   date -u +%Y%m%d-%H%M%S-utc
67 }
68
69 # Temporary file
70 function tmpf() {
71   mktemp /tmp/alirelval-XXXX
72 }
73
74 # Swallow output. Show only if something goes wrong
75 function swallow() {
76   local tout ret
77   tout=$(tmpf)
78   "$@" > "$tout" 2>&1
79   ret=$?
80   if [ $ret != 0 ] ; then
81     pr "Command failed (exit status: $ret): $@"
82     cat "$tout" >&2
83   fi
84   rm -f "$tout"
85   return $ret
86 }
87
88 # Launch a VM. Create the keypair if the given keyfile does not exist. Syntax:
89 #
90 #   RunVM <image_id> <profile> <user_data> <key_name> <key_file>
91 #
92 # Returns 0 on success, nonzero on failure. IP address is returned on stdout.
93 function RunVM() {
94   local imageId profile userData keyName
95   imageId="$1"
96   profile="$2"
97   userData="$3"
98   keyName="$4"
99   keyFile="$5"
100   local raw iip iid ret attempt createdKeypair error
101
102   # keypair part: if file does not exist, create keypair
103   if [ ! -e "$keyFile" ] ; then
104     pr "Creating a new keypair: $keyName (private key: $keyFile)"
105     swallow euca-create-keypair -f "$keyFile" "$keyName"
106     if [ $? != 0 ] ; then
107       pr 'Problems creating the keypair'
108       return $errCreateKey
109     fi
110     createdKeypair=1
111   fi
112
113   attempt=0
114   pr 'Attempting to run virtual machine'
115
116   # resubmit loop
117   while true ; do
118
119     if [ $((++attempt)) -gt $maxVmLaunchAttempts ] ; then
120       pr " * Reached maximum number of attempts, giving up"
121       if [ "$createdKeypair" == 1 ] ; then
122         ( euca-delete-keypair "$keyName" ; rm -f "$keyFile" ) > /dev/null 2>&1
123       fi
124       return $errRunVm
125     elif [ $attempt != 1 ] ; then
126       pr " * Pausing between retries"
127       sleep 5
128     fi
129
130     pr -n " * Launching VM (attempt #$attempt/$maxVmLaunchAttempts)..."
131     error=0
132
133     raw=$( euca-run-instances "$imageId" -t "$profile" -d "$userData" -k "$keyName" 2>&1 )
134     ret=$?
135     iid=$( echo "$raw" | egrep '^INSTANCE' | head -n1 | awk '{ print $2 }' )
136     if [ $ret != 0 ] || [ "$iid" == '' ] ; then
137       # 'hard' error, but can be temporary
138       pr 'error: message follows'
139       echo "$raw" >&2
140       sleep 1
141       continue
142     else
143       pr 'ok'
144     fi
145
146     pr " * VM has instance ID $iid"
147     pr -n " * Waiting for IP address..."
148
149     # wait for address loop
150     iip=''
151     for ((i=0; i<$maxVmAddressWait; i++)) ; do
152       sleep 1
153       raw=$( euca-describe-instances 2>&1 | grep -E '^INSTANCE' | grep "$iid" | head -n1 )
154
155       # error state?
156       echo "$raw" | grep -i error -q
157       if [ $? == 0 ] ; then
158         pr ; pr " * VM went to error state"
159         error=1
160         break
161       fi
162
163       # no error: try to parse address (NOTE: only IPv4 for the moment)
164       iip=$( echo "$raw" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' )
165       if [ "$iip" != '' ] ; then
166         pr
167         break
168       fi
169
170       # no address
171       pr -n '.'
172
173     done
174
175     # do we have address?
176     if [ "$iip" != '' ] ; then
177       pr " * VM has address $iip"
178       break
179     fi
180
181     # we don't: terminate (timeout)
182     [ "$error" != 1 ] && pr 'timeout'
183     pr " * Terminating instance $iid"
184     euca-terminate-instances "$iid" > /dev/null 2>&1
185
186   done
187
188   # success
189   [ "$createdKeypair" == 1 ] && euca-delete-keypair "$keyName" > /dev/null 2>&1
190   echo "$iid $iip" # must be parsed
191   return 0
192
193 }
194
195 # Prepare the validation session directory. Syntax:
196 #
197 #   PrepareSession <aliroot_tag> <new_session_name>
198 #
199 # Returns 0 on success, nonzero on failure. Session tag returned on stdout.
200 function PrepareSession() {
201   local aliRootTag sessionTag sessionDir
202   aliRootTag="$1"
203
204   # session tag can be "auto" or any user-specified value
205   if [ "$2" != 'auto' ] ; then
206     sessionTag="$2"
207   else
208     sessionTag="${aliRootTag}_$(ndate)"
209   fi
210   shift 2
211   sessionDir="$sessionPrefix/$sessionTag"
212
213   # session directory already exists? abort
214   if [ -d "$sessionDir" ] ; then
215     pr "Session directory already exists, aborting"
216     return $errSessionDir
217   fi
218
219   # create working directory
220   mkdir -p "$sessionDir"
221   if [ $? != 0 ] ; then
222     pr "Fatal: cannot create session directory $sessionDir"
223     return $errSessionDir
224   fi
225
226   # aliroot version written to a file
227   echo "$aliRootTag" > "$sessionDir/aliroot-version.txt"
228
229   # benchmark script, benchmark config, cloud config and file list
230   cp -L benchmark.sh cloud.config benchmark.config files.list "$sessionDir/"
231   if [ $? != 0 ] ; then
232     pr "Cannot copy configuration files to $sessionDir"
233     rm -rf "$sessionDir"
234     return $errSessionDir
235   fi
236
237   # append local files to the configuration
238   for f in benchmark.config.d/*.config ; do
239     [ ! -e "$f" ] && continue
240     ( echo ''
241       echo "### from $f ###"
242       cat $f
243       echo ''
244     ) >> "$sessionDir/benchmark.config"
245   done
246
247   # command-line options override the configuration
248   if [ $# != 0 ] ; then
249     pr "Note: the following command-line options will override the corresponding ones in the config files:"
250     ( echo ''
251       echo "### from the command line ###"
252       while [ $# -gt 0 ] ; do
253         extraName="${1%%=*}"
254         extraVal="${1#*=}"
255         if [ "$extraName" != "$1" ] ; then
256           pr " * $extraName = $extraVal"
257           echo "$1"
258         fi
259         shift
260       done
261       echo ''
262     ) >> "$sessionDir/benchmark.config"
263   fi
264
265   # success: return the session tag and move to the session directory
266   pr "*** Creating new working session: $sessionTag ***"
267   pr "*** Use this name for future session operations ***"
268   echo "$sessionTag"
269   return 0
270 }
271
272 # Undo the previous action
273 function PrepareSession_Undo() {
274   rm -rf "$sessionPrefix/$1"
275 }
276
277 # Recycle the VM from an existing session
278 function RecycleSession() {
279   local sessionTag="$1"
280   local fromSessionTag="$2"
281   local fromSessionDir="$sessionPrefix/$fromSessionTag"
282   local f
283
284   for f in 'instance-id.txt' 'instance-address.txt' 'key.pem' ; do
285     cp -L "$fromSessionDir/$f" "$f" > /dev/null 2>&1
286     if [ $? != 0 ] ; then
287       pr "Cannot copy $f from the source session dir $fromSessionDir"
288       return $errRecycleSession
289     fi
290   done
291
292   return 0
293 }
294
295 # Move into the session tag directory. Usage:
296 #
297 #   MoveToSessionDir <session_tag>
298 #
299 # Returns 0 on success, nonzero on error.
300 function MoveToSessionDir() {
301   originalWorkDir="$PWD"
302   cd "$sessionPrefix/$sessionTag" || return $errSessionDir
303   return 0
304 }
305
306 # Undo the previous action
307 function MoveToSessionDir_Undo() {
308   cd "$originalWorkDir"
309 }
310
311 # Load the benchmark configuration
312 function LoadConfig() {
313   source cloud.config > /dev/null 2>&1
314   if [ $? != 0 ] ; then
315     pr "Cannot load benchmark configuration"
316     return $errCfg
317   fi
318   return 0
319 }
320
321 # Instantiate the validation VM
322 function InstantiateValidationVM() {
323   local sessionTag instanceId instanceIp ret raw
324   sessionTag="$1"
325
326   # check if we already have a vm
327   instanceId="$(cat instance-id.txt 2> /dev/null)"
328   if [ "$instanceId" != '' ] ; then
329     pr "Virtual machine $instanceId is already running"
330     return 0 # consider it a success
331   else
332     rm -f instance-id.txt instance-address.txt
333   fi
334
335   # do we need to create a keypair?
336   if [ "$cloudKeyName" == '' ] ; then
337     pr "Note: temporary SSH keys will be created for this VM"
338     cloudKeyName="$sessionTag"
339     cloudKeyFile="$PWD/key.pem"
340     rm -f "$cloudKeyFile"
341   elif [ -e "$cloudKeyFile" ] ; then
342     # copy key to session dir
343     pr -n "Copying private key $cloudKeyFile to session directory..."
344     rm -f 'key.pem'
345     cp -L "$cloudKeyFile" 'key.pem' 2> /dev/null
346     if [ $? != 0 ] ; then
347       pr 'error'
348       return $errCopyKey
349     else
350       pr 'ok'
351     fi
352     cloudKeyFile="$PWD/key.pem"
353   else
354     pr "Cannot find private key to access virtual machines: $cloudKeyFile"
355     return $errCopyKey
356   fi
357
358   # launch virtual machine and get its address
359   raw=$( RunVM "$cloudImageId" "$cloudProfile" "$cloudUserData" "$cloudKeyName" "$cloudKeyFile" )
360   ret=$?
361
362   if [ $ret == 0 ] ; then
363     instanceId=$( echo $raw | cut -d' ' -f1 )
364     instanceIp=$( echo $raw | cut -d' ' -f2 )
365
366     # write both parameters to files
367     echo $instanceId > 'instance-id.txt'
368     echo $instanceIp > 'instance-address.txt'
369   fi
370
371   return $ret
372 }
373
374 # Undo the previous action
375 function InstantiateValidationVM_Undo() {
376   local sessionTag
377   sessionTag="$1"
378   if [ -e 'instance-id.txt' ] ; then
379     swallow euca-terminate-instances $(cat instance-id.txt)
380     if [ $? == 0 ] ; then
381       rm -f instance-id.txt instance-address.txt key.pem
382     fi
383   fi
384 }
385
386 # Generic SSH function to the VM
387 function VMSSH() {
388   local instanceIp sshParams ret
389   instanceIp=$(cat instance-address.txt 2> /dev/null)
390   sshParams="-oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -oPasswordAuthentication=no -i $PWD/key.pem"
391
392   if [ "$1" == '--rsync-cmd' ] ; then
393     shift
394     echo ssh $sshParams "$@"
395     ret=0
396   else
397     ssh $sshParams "$cloudUserName"@"$instanceIp" "$@"
398     ret=$?
399   fi
400   return $ret
401 }
402
403 # Opens a shell to the remote VM
404 function Shell() {
405   local sessionTag
406   sessionTag="$1"
407   VMSSH
408 }
409
410 # Checks status of the validation
411 function Status() {
412   local raw ret screen exitcode sessionTag
413   sessionTag="$1"
414   raw=$( VMSSH -t "screen -ls 2> /dev/null | grep -q .${screenPrefix}-${sessionTag} && echo -n 'screen_yes ' || echo -n 'screen_no ' ; cat $sessionTag/validation.done 2> /dev/null || echo 'not_done' ; true" 2> /dev/null )
415   raw=$( echo "$raw" | tr -cd '[:alnum:]_ ' ) # garbage removal
416   ret=$?
417
418   if [ "$ret" != 0 ] ; then
419     pr "Cannot get status"
420     return $errStatusUnavailable
421   fi
422
423   screen="${raw%% *}"
424   exitcode="${raw#* }"
425
426   if [ "$screen" == 'screen_yes' ] ; then
427     pr 'Status: validation still running'
428     return $errStatusRunning
429   else
430     if [ "$exitcode" == 'not_done' ] ; then
431       pr 'Status: validation not running'
432       return $errStatusNotRunning
433     elif [ "$exitcode" == 0 ] ; then
434       pr 'Status: validation completed successfully'
435       return $errStatusDoneOk
436     else
437       pr "Status: validation finished with errors (exitcode: $exitcode)"
438       return $errStatusDoneFail
439     fi
440   fi
441
442 }
443
444 # Wait for host to be ready
445 function WaitSsh() {
446   local attempt error
447   attempt=0
448   pr -n 'Waiting for the VM to accept SSH connections...'
449
450   while ! VMSSH -Tq true > /dev/null 2>&1 ; do
451     if [ $((++attempt)) -gt $maxSshConnectAttempts ] ; then
452       pr 'timeout'
453       error=1
454       break
455     fi
456     pr -n '.'
457     sleep 3
458   done
459
460   [ "$error" == 1 ] && return $errSshNotReady
461   pr 'ok'
462   return 0
463 }
464
465 # Run the validation
466 function Validate() {
467   local instanceIp sshParams sessionTag
468   sessionTag="$1"
469   instanceIp=$(cat instance-address.txt 2> /dev/null)
470   sshParams="-oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -oPasswordAuthentication=no -i $PWD/key.pem"
471
472   # create helper script to launch benchmark
473   cat > run-benchmark.sh <<_EoF_
474 #!/bin/bash
475 export LANG=C
476 cd \$(dirname "\$0")
477 v=validation.done
478 rm -f \$v
479 env ALIROOT_VERSION=$(cat aliroot-version.txt) ./benchmark.sh run $sessionTag files.list benchmark.config 2>&1 | tee run-benchmark.log
480 #sleep 1000
481 ret=\$?
482 echo \$ret > \$v
483 echo ; echo ; echo
484 echo "*** Validation finished with exitcode \$ret ***"
485 echo ; echo ; echo
486 read -p 'Press ENTER to dismiss: automatic dismiss in 60 seconds...' -t 60
487 _EoF_
488   chmod +x run-benchmark.sh
489
490   # transfer files
491   pr 'Transferring files to the VM'
492   rsync -av -e "$(VMSSH --rsync-cmd)" $PWD/ $cloudUserName@$instanceIp:$sessionTag/ || return $errLaunchValidation
493
494   # open a screen that does something; note that the command is not executed if
495   # the screen already exists, which is what we want
496   # note: sleep necessary to avoid "dead" screens
497   VMSSH -t "screen -wipe > /dev/null 2>&1 ; if screen -ls | grep -q ${screenPrefix}-${sessionTag} ; then ret=42 ; else screen -dmS ${screenPrefix}-${sessionTag} $sessionTag/run-benchmark.sh ; ret=0 ; sleep 3 ; fi ; exit \$ret"
498   ret=$?
499
500   # message
501   if [ $ret == 42 ] ; then
502     pr 'Validation already running inside a screen.'
503   else
504     pr 'Validation launched inside a screen.'
505   fi
506
507   pr
508   pr 'Check the progress status with:'
509   pr "  $Prog --session $sessionTag --status"
510   pr 'Attach to the screen for debug:'
511   pr "  $Prog --session $sessionTag --attach"
512   pr 'Open a shell to the virtual machine:'
513   pr "  $Prog --session $sessionTag --shell"
514   pr
515
516   # ignore ssh errors
517   return 0
518 }
519
520 # Attach current validation screen, if possible
521 function Attach() {
522   local sessionTag
523   sessionTag="$1"
524
525   VMSSH -t "( screen -wipe ; screen -rx ${screenPrefix}-${sessionTag} ) > /dev/null 2>&1"
526
527   if [ $? != 0 ] ; then
528     pr "Cannot attach screen: check if validation is running with:"
529     pr "  $Prog --session $sessionTag --status"
530     pr "or connect manually to the VM for debug:"
531     pr "  $Prog --session $sessionTag --attach"
532     return $errAttachScreen
533   fi
534
535   return 0
536 }
537
538 # Pick session interactively
539 function PickSession() {
540   local sessionTag sess listSessions mess
541   mess="$1"
542   listSessions=()
543   mkdir -p "$sessionPrefix"
544
545   while read sess ; do
546     [ ! -d "$sessionPrefix/$sess" ] && continue
547     listSessions+=( $sess )
548   done < <( cd $sessionPrefix ; ls -1t )
549
550   if [ ${#listSessions[@]} == 0 ] ; then
551     pr "No session available in session directory $sessionPrefix"
552     return $errPickSession
553   fi
554
555   # print user message if provided
556   [ "$mess" != '' ] && pr "$mess"
557
558   pr 'Available sessions (most recent first):'
559   for ((i=0; i<${#listSessions[@]}; i++)) ; do
560     pr "$( printf "  % 2d. ${listSessions[$i]}" $((i+1)) )"
561   done
562   pr -n 'Pick one: '
563   read i
564
565   let i--
566   if [ "$i" -lt 0 ] || [ "${listSessions[$i]}" == '' ] ; then
567     pr 'Invalid session'
568     return $errPickSession
569   fi
570
571   sess="${listSessions[$i]}"
572   pr "You chose session $sess"
573   echo $sess
574   return 0
575 }
576
577 # Run an action
578 function RunAction() {
579   local ret
580   type "$1" > /dev/null 2>&1
581   if [ $? == 0 ] ; then
582     #pr "--> $1 (wd: $PWD)"
583     eval "$@"
584     ret=$?
585     #pr "<-- $1 (ret: $ret, wd: $PWD)"
586     return $ret
587   fi
588   return 0
589 }
590
591 # Print help screen
592 function Help() {
593   pr "$Prog -- by Dario Berzano <dario.berzano@cern.ch>"
594   pr 'Controls the Release Validation workflow on the cloud for AliRoot.'
595   pr
596   pr "Usage 1: $Prog [--prepare|--launch|--recycle] [--from-session] --aliroot <aliroot_tag> [--session <custom_session_tag>] [-- arbitraryOpt1=value [arbitraryOpt2=value2...]]"
597   pr
598   pr 'A new session is created to validate the specified AliRoot tag.'
599   pr
600   pr '  --prepare  : prepares the session directory containing the files needed'
601   pr '               for the validation'
602   pr '  --recycle  : prepares a new session by recycling the head node from an'
603   pr '               existing one. Source session is specified via the'
604   pr '               --from-session switch or it can be interactively selected'
605   pr '  --launch   : launches the full validation process: prepares session,'
606   pr '               runs the virtual machine, launches the validation program'
607   pr '  --aliroot  : the AliRoot tag to validate, in the form "vAN-20140610"'
608   pr '  --session  : custom session name to provide to the validation session:'
609   pr '               if omitted, defaults to <aliroot_tag>_<utc_datetime_now>'
610   pr
611   pr 'Arbitrary options (in the form variable=value) can be specified after the'
612   pr 'double dash and will override the corresponding options in any of the'
613   pr 'configuration files.'
614   pr ; pr
615   pr "Usage 2: $Prog [--runvm|--validate|--shell|--status] --session <session_tag>"
616   pr
617   pr 'Runs the validation step by step after a session is created with'
618   pr '--prepare, and runs other actions on a certain session.'
619   pr
620   pr '  --session  : session identifier, e.g. vAN-20140610_20140612-123047-utc:'
621   pr '               if no session is specified an interactive prompt is'
622   pr '               presented'
623   pr '  --runvm    : instantiates the head node of the validation cluster on'
624   pr '               the cloud' 
625   pr '  --validate : runs the validation script on the head node for the'
626   pr '               current session. Head node must be already up, or it'
627   pr '               should be created with --runvm. If validation is running'
628   pr '               already, connects to the existing validation shell'
629   pr '  --attach   : attach a currently running validation screen; remember to'
630   pr '               detach with Ctrl+A+D (and *not* Ctrl-C)'
631   pr '  --shell    : does SSH on the head node'
632   pr '  --status   : returns the status of the validation'
633   pr ; pr
634   pr 'Example 1: run the validation of AliRoot tag vAN-20140610:'
635   pr
636   pr "  $Prog --aliroot vAN-20140610 --launch"
637   pr
638   pr 'Example 2: do the same thing step-by-step:'
639   pr
640   pr "  $Prog --aliroot vAN-20140610 --prepare"
641   pr "  $Prog --runvm"
642   pr "  $Prog --validate"
643   pr
644 }
645
646 # The main function
647 function Main() {
648
649   # local variables
650   local Args aliRootTag EnterShell Actions sessionTag fromSessionTag
651   Actions=()
652
653   # parse command line options
654   while [ $# -gt 0 ] ; do
655     case "$1" in
656
657       # options
658       --aliroot|-a)
659         aliRootTag="$2"
660         shift 2
661       ;;
662       --session)
663         sessionTag="$2"
664         shift 2
665       ;;
666       --from-session)
667         fromSessionTag="$2"
668         shift 2
669       ;;
670
671       # actions
672       --launch)
673         # all actions
674         Actions=( PrepareSession MoveToSessionDir LoadConfig InstantiateValidationVM WaitSsh Validate )
675         shift
676       ;;
677       --prepare)
678         Actions=( PrepareSession MoveToSessionDir )
679         shift
680       ;;
681       --recycle)
682         Actions=( PrepareSession MoveToSessionDir RecycleSession )
683         shift
684       ;;
685       --runvm)
686         Actions=( MoveToSessionDir LoadConfig InstantiateValidationVM )
687         shift
688       ;;
689       --validate)
690         Actions=( MoveToSessionDir LoadConfig WaitSsh Validate )
691         shift
692       ;;
693       --attach)
694         Actions=( MoveToSessionDir LoadConfig WaitSsh Attach )
695         shift
696       ;;
697
698       # extra actions
699       --shell)
700         Actions=( MoveToSessionDir LoadConfig WaitSsh Shell )
701         shift
702       ;;
703       --status)
704         Actions=( MoveToSessionDir LoadConfig WaitSsh Status )
705         shift
706       ;;
707       --help)
708         Help
709         exit 0
710       ;;
711
712       # end of options
713       --)
714         shift
715         break
716       ;;
717
718       *)
719         pr "Invalid option: $1. Use --help for assistance."
720         return $errInvalidOpt
721       ;;
722     esac
723   done
724
725   # check for the presence of the required tools in the $PATH
726   for T in euca-describe-instances euca-describe-regions euca-run-instances euca-create-keypair euca-delete-keypair rsync ; do
727     which "$T" > /dev/null 2>&1
728     if [ $? != 0 ] ; then
729       pr "Cannot find one of the required commands: $T"
730       return $errMissingCmd
731     fi
732   done
733
734   # test EC2 credentials
735   # euca-describe-regions > /dev/null 2>&1
736   # if [ $? != 0 ] ; then
737   #   pr 'Cannot authenticate to EC2.'
738   #   pr 'Note: you must have at least the following variables properly set in your environment:'
739   #   pr "  * EC2_URL (current value: ${EC2_URL-<not set>})"
740   #   pr "  * EC2_ACCESS_KEY (current value: ${EC2_ACCESS_KEY-<not set>})"
741   #   pr "  * EC2_SECRET_KEY (current value: ${EC2_SECRET_KEY-<not set>})"
742   #   return $errEc2Auth
743   # fi
744
745   # what to do?
746   if [ ${#Actions[@]} == 0 ] ; then
747     pr 'Nothing to do. Use --help for assistance.'
748     return $errInvalidOpt
749   fi
750
751   # run actions
752   for ((i=0; i<${#Actions[@]}; i++)) ; do
753
754     A=${Actions[$i]}
755
756     if [ "$A" == 'PrepareSession' ] ; then
757       # special action returning the session tag
758       if [ "$aliRootTag" == '' ] ; then
759         pr 'Specify an AliRoot version with --aliroot <tag>'
760         return $errInvalidOpt
761       fi
762       [ "$sessionTag" == '' ] && sessionTag='auto'
763       sessionTag=$( RunAction "$A" "$aliRootTag" "$sessionTag" "$@" )
764       ret=$?
765     elif [ "$A" == 'RecycleSession' ] ; then
766       # special action requiring additional parameters
767       if [ "$fromSessionTag" == '' ] ; then
768         fromSessionTag=$( PickSession 'Select a source session to recycle.' )
769         ret=$?
770         [ $ret != 0 ] && break
771       fi
772       RunAction "$A" "$sessionTag" "$fromSessionTag"
773       ret=$?
774     else
775       if [ "$sessionTag" == '' ] ; then
776         sessionTag=$( PickSession )
777         ret=$?
778         [ $ret != 0 ] && break
779       fi
780       RunAction "$A" "$sessionTag"
781       ret=$?
782     fi
783
784     # 100 to 140 --> not errors
785     ( [ $ret != 0 ] && ( [ $ret -ge 100 ] || [ $ret -le 140 ] ) ) && break
786
787   done
788
789   # undo actions
790   let i--
791   if [ $ret != 0 ] && ( [ $ret -ge 100 ] || [ $ret -le 140 ] ) ; then
792     for ((; i>=0; i--)) ; do
793       RunAction "${Actions[$i]}_Undo" "$sessionTag"
794     done
795   fi
796
797   # return last value
798   return $ret
799
800 }
801
802 #
803 # Entry point
804 #
805
806 Main "$@" || exit $?