]> git.uio.no Git - u/mrichter/AliRoot.git/blob - PWGPP/QA/scripts/alienSync.sh
make it work on a mac
[u/mrichter/AliRoot.git] / PWGPP / QA / scripts / alienSync.sh
1 #!/bin/bash
2 #
3 #  - script to sync a group of files on alien with a local cache
4 #    downloads only new and updated files
5 #  - by default it mirrors the directory structure in a specified local location
6 #    (the local chache location and paths can be manipulated.)
7 #  - needs a configured config file (by default alienSync.config)
8 #    and a working alien environment (token and at least $ALIEN_DIR or $ALIEN_ROOT set)
9 #
10 #  origin: Mikolaj Krzewicki, mikolaj.krzewicki@cern.ch
11 #
12 main()
13 {
14   if [[ $# -lt 1 ]]; then
15     echo "Usage:  ${0##*/} configFile=/path/to/config"
16     echo "expert: ${0##*/} alienFindCommand=\"alien_find /some/path/ file\" [opt=value]"
17     echo "        ${0##*/} alienFindCommand=\"alien_find /some/path/ file\" localPathPrefix=\${PWD}"
18     echo
19     echo "by default files are downloaded to current dir, or \${alienSync_localPathPrefix} if set."
20     echo "At least specify alienFindCommand, either on command line or in the configFile."
21     return
22   fi
23   
24   # try to load the config file
25   #[[ ! -f $1 ]] && echo "config file $1 not found, exiting..." | tee -a $logFile && exit 1
26   if ! parseConfig "$@"; then return 1; fi
27
28   [[ -z ${alienFindCommand} ]] && echo "alienFindCommand not defined!" && return 1
29
30   #if not set, use the default group
31   [[ -z ${alienSyncFilesGroupOwnership} ]] && alienSyncFilesGroupOwnership=$(id -gn)
32
33   # do some accounting
34   [[ ! -d $logOutputPath ]] && echo "logOutputPath not available, creating..." && mkdir -p $logOutputPath && chgrp ${alienSyncFilesGroupOwnership} ${logOutputPath}
35   [[ ! -d $logOutputPath ]] && echo "could not create log dir, exiting..." && exit 1
36   dateString=$(date +%Y-%m-%d-%H-%M)
37   logFile=$logOutputPath/alienSync-$dateString.log
38   echo "$0 $@"|tee -a $logFile
39   echo ""|tee -a $logFile
40   echo log: $logFile
41   
42   #be nice and allow group members access as well (002 will create dirs with 775 and files with 664)
43   umask 0002
44
45   #lock
46   lockFile=$logOutputPath/runningNow.lock
47   [[ -f $lockFile ]] && echo "locked. Another process running? ($lockFile)" | tee -a $logFile && exit 1
48   touch $lockFile
49   [[ ! -f $lockFile ]] && echo "unable to create lock. exiting..." | tee -a $logFile && exit 1
50
51   #redirect all output to a log
52   if [[ $allOutputToLog -eq 1 ]]; then
53     exec 6>&1
54     exec 1>$logFile 2>&1
55   fi
56   
57   newFilesList=$logOutputPath/"newFiles.list"
58   rm -f $newFilesList
59   touch $newFilesList
60   redoneFilesList=$logOutputPath/"redoneFiles.list"
61   rm -f $redoneFilesList
62   touch $redoneFilesList
63   updatedFilesList="${logOutputPath}/updatedFiles.list"
64
65   # check the config
66   [[ -z $alienFindCommand ]] && echo "alienFindCommand not defined, exiting..." && exitScript 1
67   [[ -z ${localPathPrefix} ]] && echo "localPathPrefix not defined, exiting..." && exitScript 1
68   [[ -z $logOutputPath ]] && echo "logOutputPath not defined, exiting..." && exitScript 1
69   [[ -z $secondsToSuicide ]] && echo "setting default secondsToSuicide of 10 hrs..." && secondsToSuicide=$(( 10*3600 ))
70
71   # init alien 
72   [[ -z $ALIEN_ROOT && -n $ALIEN_DIR ]] && ALIEN_ROOT=$ALIEN_DIR
73   #if ! haveAlienToken; then
74   #  $ALIEN_ROOT/api/bin/alien-token-destroy
75     $ALIEN_ROOT/api/bin/alien-token-init $alienUserName
76   #fi
77   #if ! haveAlienToken; then
78   #  if [[ $allOutputToLog -eq 1 ]]; then
79   #    exec 1>&6 6>&-
80   #  fi
81   #  echo "problems getting token! exiting..." | tee -a $logFile
82   #  exitScript 1
83   #fi
84   #ls -ltr /tmp/gclient_env_$UID
85   #cat /tmp/gclient_env_$UID
86   source /tmp/gclient_env_$UID
87
88   #set a default timeout for grid access
89   [[ -z $copyTimeout ]] && copyTimeout=600
90   export GCLIENT_COMMAND_MAXWAIT=$copyTimeout
91
92   localAlienDatabase=$logOutputPath/localAlienDatabase.list
93   localFileList=$logOutputPath/localFile.list
94   
95   alienFileListCurrent=$logOutputPath/alienFileDatabase.list
96   [[ ! -f $localFileList ]] && touch $localFileList
97   candidateLocalFileDatabase=$logOutputPath/candidateLocalFileDatabase.list
98
99   #here we produce the current alien file list
100   if [[ -n ${useExistingAlienFileDatabase} && -f ${localAlienDatabase} ]]; then
101     #we use the old one
102     echo "using ${localAlienDatabase} instead of full alien search"
103     echo cp -f ${localAlienDatabase} ${alienFileListCurrent}
104     cp -f ${localAlienDatabase} ${alienFileListCurrent}
105   else
106     #we make a new one
107     echo "eval $alienFindCommand > $alienFileListCurrent"
108     eval "$alienFindCommand" > $alienFileListCurrent
109   fi
110
111   echo "number of files in the collection: $(wc -l $alienFileListCurrent)"
112   #create a list of candidate destination locations
113   #this is in case there are more files on alien trying to get to the same local destination
114   #in which case we take the one with the youngest ctime (later in code)
115   if [[ -n ${destinationModifyCommand} ]]; then
116     echo eval "cat $alienFileListCurrent | ${destinationModifyCommand} | sed \"s,^,${localPathPrefix},\"  > ${candidateLocalFileDatabase}"
117     eval "cat $alienFileListCurrent | ${destinationModifyCommand} | sed \"s,^,${localPathPrefix},\"  > ${candidateLocalFileDatabase}"
118   fi
119
120   #logic is: if file list is missing we force the md5 recalculation
121   [[ ! -f $localAlienDatabase ]] && forceLocalMD5recalculation=1 && echo "forcing local MD5 sum recalculation" && cp -f $alienFileListCurrent $localAlienDatabase
122
123   #since we grep through the files frequently, copy some stuff to tmpfs for fast access
124   tmp=$(mktemp -d 2>/dev/null)
125   if [[ -d $tmp ]]; then
126     cp $localAlienDatabase $tmp
127     cp $localFileList $tmp
128     cp $alienFileListCurrent $tmp
129     [[ -f ${candidateLocalFileDatabase} ]] && cp ${candidateLocalFileDatabase} ${tmp}
130   else
131     tmp=$logOutputPath
132   fi
133
134   echo "starting downloading:"
135   lineNumber=0
136   alienFileCounter=0
137   localFileCounter=0
138   downloadedFileCounter=0
139   while read -r alienFile md5alien timestamp size
140   do
141     ((lineNumber++))
142     
143     #sometimes the md5 turns out empty and is then stored as a "." to avoid problems parsing
144     [[ "$md5alien" =~ "." ]] && md5alien=""
145     
146     [[ -n $timeStampInLog ]] && date
147     [[ $SECONDS -ge $secondsToSuicide ]] && echo "$SECONDS seconds passed, exiting by suicide..." && break
148     [[ "$alienFile" != "/"*"/"?* ]] && echo "WARNING: read line not path-like: $alienFile" && continue
149     ((alienFileCounter++))
150     destination=${localPathPrefix}/${alienFile}
151     destination=${destination//\/\///} #remove double slashes
152     [[ -n ${destinationModifyCommand} ]] && destination=$( eval "echo ${destination} | ${destinationModifyCommand}" )
153     destinationdir=${destination%/*}
154     [[ -n $softLinkName ]] && softlinktodestination=${destinationdir}/${softLinkName}
155     tmpdestination="${destination}.aliensyncTMP"
156
157     if [[ -n ${destinationModifyCommand} ]]; then
158       #find the candidate in the database, in case there are more files trying to go to the same
159       #place due to $destinationModifyCommand which alters the final path, find the one
160       #with the largest ctime (3rd field in the database list) and check if that is the current one
161       #if not - skip
162       #echo grep -n ${destination} $candidateLocalFileDatabase | sed "s/:/ /"  | sort -rk4
163       #grep -n ${destination} $candidateLocalFileDatabase| sed "s/:/ /"  | sort -rk4
164       #this guy contains: index of the original entry, local file name, md5, ctime
165       candidateDBrecord=($(grep -n ${destination} $tmp/${candidateLocalFileDatabase##*/}| sed "s/:/ /"  | sort -rk4|head -n1 ))
166       originalEntryIndex=${candidateDBrecord[0]}
167       [[ $lineNumber -ne $originalEntryIndex ]] && continue
168     fi
169     
170     redownloading=""
171     if [[ -f ${destination} ]]; then
172       #soft link the downloaded file (maybe to provide a consistent link to the latest version)
173       if [[ -n $softlinktodestination ]]; then
174         echo ln -sf ${destination} ${softlinktodestination}
175         ln -sf ${destination} ${softlinktodestination}
176       fi
177       ((localFileCounter++))
178       
179       localDBrecord=($(grep $alienFile $tmp/${localAlienDatabase##*/}))
180       md5local=${localDBrecord[1]}
181
182       #sometimes the md5 turns out empty and is then stored as a "." to avoid problems parsing
183       [[ "$md5local" =~ "." ]] && md5local=""
184
185       if [[ $forceLocalMD5recalculation -eq 1 || -z $md5local ]]; then
186         tmparrayMD5=($(md5sum ${destination}))
187         md5recalculated=${tmparrayMD5[0]}
188         [[ "$md5local" != "$md5recalculated" ]] && echo "WARNING: local copy change ${destination}"
189         md5local=${md5recalculated}
190       fi
191       if [[ "$md5local" == "$md5alien" && -n $md5alien ]]; then
192         echo "OK ${destination} $md5alien"
193         if ! grep -q ${destination} $tmp/${localFileList##*/}; then
194           echo ${destination} >> $localFileList
195         fi
196         continue
197       fi
198       if [[ -z $md5alien ]]; then
199         if ! grep -q ${destination} $tmp/${localFileList##*/}; then
200           echo ${destination} >> $localFileList
201         fi
202         echo "WARNING: missing alien md5, leaving the local file as it is"
203         continue
204       fi
205       echo "WARNING: md5 mismatch ${destination}"
206       echo "  $md5local $md5alien"
207       redownloading=1
208     fi
209     
210     [[ -f $tmpdestination ]] && echo "WARNING: stale $tmpdestination, removing" && rm $tmpdestination
211     
212     mkdir -p ${destinationdir} && chgrp ${alienSyncFilesGroupOwnership} ${destinationdir}
213     [[ ! -d $destinationdir ]] && echo cannot access $destinationdir && continue
214
215     #check token
216     #if ! haveAlienToken; then
217     #  $ALIEN_ROOT/api/bin/alien-token-init $alienUserName
218     #  #source /tmp/gclient_env_$UID
219     #fi
220     
221     export copyMethod
222     export copyScript
223     export copyTimeout
224     export copyTimeoutHard
225     echo copyFromAlien "$alienFile" "$tmpdestination"
226     [[ $pretend -eq 1 ]] && continue
227     copyFromAlien $alienFile $tmpdestination
228     chgrp ${alienSyncFilesGroupOwnership} $tmpdestination
229
230     # if we didn't download remove the destination in case we tried to redownload 
231     # a corrupted file
232     [[ ! -f $tmpdestination ]] && echo "file not downloaded" && rm -f ${destination} && continue
233
234     downloadOK=0
235     #verify the downloaded md5 if available, validate otherwise...
236     if [[ -n $md5alien ]]; then
237       if (echo "$md5alien  $tmpdestination"|md5sum -c --status -); then
238         echo "OK md5 after download"
239         downloadOK=1
240       else
241         echo "failed verifying md5 $md5alien of $tmpdestination"
242       fi
243     else
244       downloadOK=1
245     fi
246
247     #handle zip files - check the checksums
248     if [[ $alienFile =~ '.zip' && $downloadOK -eq 1 ]]; then
249       echo "checking integrity of zip archive $tmpdestination"
250       if unzip -t $tmpdestination; then
251         downloadOK=1
252       else
253         downloadOK=0
254       fi
255     fi
256
257     if [[ $downloadOK -eq 1 ]]; then
258       echo mv $tmpdestination ${destination}
259       mv $tmpdestination ${destination}
260       chgrp ${alienSyncFilesGroupOwnership} ${destination}
261       ((downloadedFileCounter++))
262       if [[ -n $softlinktodestination ]]; then
263         echo ln -s ${destination} $softlinktodestination
264         ln -s ${destination} $softlinktodestination
265       fi
266       [[ -z $redownloading ]] && echo ${destination} >> $newFilesList
267       [[ -n $redownloading ]] && echo ${destination} >> $redoneFilesList
268       if ! grep -q ${destination} $tmp/${localFileList##*/}; then
269         echo ${destination} >> $localFileList
270       fi
271       [[ -n ${postCommand} ]] && ( cd ${destinationdir}; eval "${postCommand}" )
272     else
273       echo "download not validated, NOT moving to ${destination}..."
274       echo "removing $tmpdestination"
275       rm -f $tmpdestination
276       continue
277     fi
278
279     if [[ $unzipFiles -eq 1 ]]; then
280       echo unzip $tmpdestination -d $destinationdir
281       unzip $tmpdestination -d $destinationdir
282     fi
283
284     echo
285   done < ${alienFileListCurrent}
286
287   [[ $alienFileCounter -gt 0 ]] && mv -f $alienFileListCurrent $localAlienDatabase
288
289   echo "${0##*/} DONE"
290  
291   if [[ $allOutputToLog -eq 1 ]]; then
292     exec 1>&6 6>&-
293   fi
294  
295   cat ${newFilesList} ${redoneFilesList} > ${updatedFilesList}
296   eval "${executeEnd}"
297   
298   echo alienFindCommand:
299   echo "  $alienFindCommand"
300   echo
301   echo "files on alien: $alienFileCounter"
302   echo "local files before: $localFileCounter"
303   echo "files downloaded: $downloadedFileCounter"
304   echo
305   echo "new files:"
306   echo
307   cat $newFilesList
308   echo
309   echo "redone files:"
310   echo
311   cat $redoneFilesList
312
313   [[ -n $MAILTO ]] && echo $logFile | mail -s "alienSync $alienFindCommand done" $MAILTO
314
315   exitScript 0
316 }
317
318 exitScript()
319 {
320   echo
321   echo removing $lockFile
322   rm -f $lockFile
323   echo removing $tmp
324   rm -rf $tmp
325   exit $1
326 }
327
328 alien_find()
329 {
330   # like a regular alien_find command
331   # output is a list with md5sums and ctimes
332   executable="$ALIEN_ROOT/api/bin/gbbox find"
333   [[ ! -x ${executable% *} ]] && echo "### error, no $executable..." && return 1
334   [[ -z $logOutputPath ]] && logOutputPath="./"
335
336   maxCollectionLength=10000
337
338   export GCLIENT_COMMAND_MAXWAIT=600
339   export GCLIENT_COMMAND_RETRY=20
340   export GCLIENT_SERVER_RESELECT=4
341   export GCLIENT_SERVER_RECONNECT=2
342   export GCLIENT_RETRY_DAMPING=1.2
343   export GCLIENT_RETRY_SLEEPTIME=2
344
345   iterationNumber=0
346   numberOfFiles=$maxCollectionLength
347   rm -f $logOutputPath/alien_find.err
348   while [[ $numberOfFiles -ge $maxCollectionLength && $iterationNumber -lt 100 ]]; do
349     numberOfFiles=0
350     offset=$((maxCollectionLength*iterationNumber-1)); 
351     [[ $offset -lt 0 ]] && offset=0; 
352     $executable -x coll -l ${maxCollectionLength} -o ${offset} "$@" 2>>$logOutputPath/alien_find.err \
353     | while read -a fields;
354     do
355       nfields=${#fields[*]}
356       turl=""
357       md5=""
358       ctime=""
359       size=""
360       for ((x=1;x<=${nfields};x++)); do
361         field=${fields[${x}]}
362         if [[ "${field}" == "md5="* ]]; then
363           eval ${field}
364         fi
365         if [[ "${field}" == "turl="* ]]; then
366           eval ${field}
367         fi
368         if [[ "${field}" == "ctime="* ]]; then
369           eval ${field}" "${fields[((x+1))]}
370         fi
371         if [[ "${field}" == "size="* ]]; then
372           eval ${field}" "${fields[((x+1))]}
373         fi
374       done
375       ctime=$( date -d "${ctime}" +%s 2>/dev/null)
376       [[ -z $md5 ]] && md5="."
377       [[ -n "$turl" ]] && echo "${turl//"alien://"/} ${md5} ${ctime} ${size}" && ((numberOfFiles++))
378     done
379     ((iterationNumber++))
380   done
381   return 0
382 }
383
384 alien_find_split()
385 {
386   #split the search in sub searches in the subdirectories of the base path
387   basePath=${1}
388   searchTerm=${2}
389   subPathSelection=${3}
390   [[ -z ${subPathSelection} ]] && subPathSelection=".*"
391   gbbox ls ${basePath} 2>/dev/null | \
392   while read subPath; do
393     [[ ! ${subPath} =~ ${subPathSelection} ]] && continue
394     alien_find ${basePath}/${subPath} ${searchTerm}
395   done 
396 }
397
398 listCollectionContents()
399 {
400   #find the xml collections and print the list of filenames and hashes
401   while read -a fields; do
402     nfields=${#fields[*]}
403     turl=""
404     md5=""
405     ctime=""
406     for ((x=1;x<=${nfields};x++)); do
407       field=${fields[${x}]}
408       if [[ "${field}" == "md5="* ]]; then
409         eval ${field}
410       fi
411       if [[ "${field}" == "turl="* ]]; then
412         eval ${field}
413       fi
414       if [[ "${field}" == "ctime="* ]]; then
415         eval "${field} ${fields[((x+1))]}"
416       fi
417     done
418     ctime=$( date -d "${ctime}" +%s 2>/dev/null)
419     [[ -n "$turl" ]] && echo "${turl//"alien://"/} ${md5} ${ctime}"
420   done < <(catCollections $1 $2 2>/dev/null)
421 }
422
423 catCollections()
424 {
425   #print the contents of collection(s)
426   if [[ $# -eq 2 ]]; then
427     while read collection; do
428       [[ $collection != "/"*"/"?* ]] && continue
429       gbbox cat $collection
430     done < <(alien_find $1 $2)
431   elif [[ $# -eq 1 ]]; then
432     gbbox cat $1
433   fi
434 }
435
436 haveAlienToken()
437 {
438   #only get a new token if the old one expires soon
439   maxExpireTime=$1
440   [[ -z $maxExpireTime ]] && maxExpireTime=4000
441   [[ -z $ALIEN_ROOT ]] && echo "no ALIEN_ROOT!" && return 1
442   now=$(date "+%s")
443   tokenExpirationTime=$($ALIEN_ROOT/api/bin/alien-token-info|grep Expires)
444   tokenExpirationTime=$(date -d "${tokenExpirationTime#*:}" "+%s")
445   secondsToExpire=$(( tokenExpirationTime-now ))
446   if [[ $secondsToExpire -lt $maxExpireTime ]]; then
447     return 1
448   else
449     echo "token valid for another $secondsToExpire seconds"
450     return 0
451   fi
452 }
453
454 copyFromAlien()
455 {
456   #copy the file $1 to $2 using a specified method
457   #uses the "timeout" command to make sure the 
458   #download processes will not hang forever.
459   #
460   [[ -z $copyTimeout ]] && copyTimeout=600
461   [[ -z $copyTimeoutHard ]] && copyTimeoutHard=1200
462   src=${1//"alien://"/}
463   src="alien://${src}"
464   dst=$2
465   if [[ "$copyMethod" == "tfilecp" ]]; then
466     if which timeout &>/dev/null; then
467       echo timeout $copyTimeout root -b -q "$copyScript(\"$src\",\"$dst\")"
468       timeout $copyTimeout root -b -q "$copyScript(\"$src\",\"$dst\")"
469     else
470       echo root -b -q "$copyScript(\"$src\",\"$dst\")"
471       root -b -q "$copyScript(\"$src\",\"$dst\")"
472     fi
473   else
474     if which timeout &>/dev/null; then
475       echo timeout $copyTimeout $ALIEN_ROOT/api/bin/alien_cp $src $dst
476       timeout $copyTimeout $ALIEN_ROOT/api/bin/alien_cp $src $dst
477     else
478       echo $ALIEN_ROOT/api/bin/alien_cp $src $dst
479       $ALIEN_ROOT/api/bin/alien_cp $src $dst
480     fi
481   fi
482 }
483
484 parseConfig()
485 {
486   #config file
487   configFile=""
488   alienFindCommand=""
489   secondsToSuicide=$(( 10*3600 ))
490   localPathPrefix="${PWD}"
491   #define alienSync_localPathPrefix in your env to have a default central location
492   [[ -n ${alienSync_localPathPrefix} ]] && localPathPrefix=${alienSync_localPathPrefix}
493   logOutputPath="${localPathPrefix}/alienSyncLogs"
494   unzipFiles=0
495   allOutputToLog=0
496
497   args=("$@")
498
499   #first, check if the config file is configured
500   #is yes - source it so that other options can override it
501   #if any
502   for opt in "${args[@]}"; do
503     if [[ ${opt} =~ configFile=.* ]]; then
504       eval "${opt}"
505       [[ ! -f ${configFile} ]] && echo "configFile ${configFile} not found, exiting..." && return 1
506       echo "using config file: ${configFile}"
507       source "${configFile}"
508       break
509     fi
510   done
511
512   #then, parse the options as they override the options from file
513   for opt in "${args[@]}"; do
514     if [[ ! "${opt}" =~ .*=.* ]]; then
515       echo "badly formatted option ${var}, should be: option=value, stopping..."
516       return 1
517     fi
518     local var="${opt%%=*}"
519     local value="${opt#*=}"
520     echo "${var} = ${value}"
521     export ${var}="${value}"
522   done
523 }
524
525 main "$@"