rda-python-dsquasar 3.0.1__tar.gz → 3.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rda_python_dsquasar-3.0.6/MANIFEST.in +1 -0
- {rda_python_dsquasar-3.0.1/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.6}/PKG-INFO +1 -1
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/pyproject.toml +1 -1
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/dsquasar.py +368 -125
- rda_python_dsquasar-3.0.6/src/rda_python_dsquasar/dsquasar.usg +146 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6/src/rda_python_dsquasar.egg-info}/PKG-INFO +1 -1
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar.egg-info/SOURCES.txt +2 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/LICENSE +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/README.md +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/setup.cfg +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/__init__.py +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/ds_quasar.py +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/dstacc.py +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/taccrec.py +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/tacctar.py +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar.egg-info/dependency_links.txt +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar.egg-info/entry_points.txt +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar.egg-info/requires.txt +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar.egg-info/top_level.txt +0 -0
- {rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/test/test_dsquasar.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include src/rda_python_dsquasar/*.usg
|
{rda_python_dsquasar-3.0.1/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_dsquasar
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.6
|
|
4
4
|
Summary: RDA Python package to backup and recover RDA data archives to and from GLOBUS Quasar backup server
|
|
5
5
|
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/NCAR/rda-python-dsquasar
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# to gather Web and Saved bfiles, tar them into larger (> 2GB) bfiles, and
|
|
13
13
|
# back them up onto Quasar Backup Servers, at the Globus end points of
|
|
14
14
|
# NCAR GDEX Quasar and/or NCAR GDEX Quasar Drdata, for Backup and/or
|
|
15
|
-
#
|
|
15
|
+
# disaster recovery copies, respectively. The file tarring and backing up
|
|
16
16
|
# processes are completed in utility program dsarch.
|
|
17
17
|
#
|
|
18
18
|
# Github : https://github.com/NCAR/rda-python-dsquasar.git
|
|
@@ -84,6 +84,25 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
84
84
|
self.ONESIZE = 20*self.PGLOG['ONEGBS'] # 20GB, minimal file size to tar a single file
|
|
85
85
|
self.TFCOUNT = 100 # if file count is greater, use self.MINSIZE for tar file
|
|
86
86
|
self.SUBLMTS = 2000 # file count limit for a sub-group
|
|
87
|
+
# tarring (TARACT) forks one child per tar file, so its parallel unit is the tar
|
|
88
|
+
# file: use a small per-process limit and a high cap to spread the work widely.
|
|
89
|
+
self.MPTLIMIT = 200 # tar file count per batch process for tarring
|
|
90
|
+
self.MPTMAX = 12 # maximum number of batch processes for tarring
|
|
91
|
+
# uploading (BCKACT) forks one child per BCKSIZE transfer batch (~BCKSIZE/TARSIZE
|
|
92
|
+
# tar files each), so far fewer parallel units than tar files: use a large
|
|
93
|
+
# per-process limit and a low cap to avoid over-reserving cpus.
|
|
94
|
+
self.MPBLIMIT = 900 # tar file count per batch process for uploading
|
|
95
|
+
self.MPBMAX = 4 # maximum number of batch processes for uploading
|
|
96
|
+
self.MAXRUNTIME = 23*3600 # 23 hours; stop before the 24-hour PBS walltime
|
|
97
|
+
self.ONEHOUR = 3600 # seconds; time headroom needed to finish before walltime
|
|
98
|
+
# a repeat submit normally blocks as a duplicate while the batch job is running. if
|
|
99
|
+
# that job is running but barely progressing, an extra worker is submitted instead
|
|
100
|
+
# (see pick_worker_slot). progress is measured as the fraction of the recorded
|
|
101
|
+
# dscheck work done, which is action independent because dcount counts GDEX files
|
|
102
|
+
# for -A 3 but tar files for -A 2/-A 4.
|
|
103
|
+
self.MAXWORKERS = 2 # default maximum concurrent workers per command (-W)
|
|
104
|
+
self.WORKGRACE = 6*3600 # 6 hours before a running job is judged on progress
|
|
105
|
+
self.MINWDONE = 0.01 # done fraction after WORKGRACE that counts as progressing
|
|
87
106
|
self.PGBACK = {
|
|
88
107
|
'workdir' : "{}/{}/quasar_backup".format(self.PGLOG['GDEXWORK'], self.PGLOG['COMMONUSER']),
|
|
89
108
|
'mproc' : 1,
|
|
@@ -97,6 +116,10 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
97
116
|
'maxcnt' : 10,
|
|
98
117
|
'dolock' : 1,
|
|
99
118
|
'doemail' : 0,
|
|
119
|
+
'starttime' : 0, # wall-clock start of the run, for the PBS walltime guard
|
|
120
|
+
'tardone' : 0, # tar files dispatched so far, for the finish-rate estimate
|
|
121
|
+
'maxworkers' : self.MAXWORKERS, # -W, maximum concurrent workers per command
|
|
122
|
+
'worker' : 1, # -w, this run's worker slot; >1 for an added extra worker
|
|
100
123
|
'cmd' : None
|
|
101
124
|
}
|
|
102
125
|
self.dsids = []
|
|
@@ -105,13 +128,15 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
105
128
|
self.dstart = 0
|
|
106
129
|
|
|
107
130
|
# read in command line parameters
|
|
108
|
-
def read_parameters(self):
|
|
131
|
+
def read_parameters(self):
|
|
109
132
|
self.PGLOG['LOGFILE'] = "dsquasar.log" # set different log file
|
|
133
|
+
self.set_help_path(__file__)
|
|
110
134
|
self.dssdb_dbname()
|
|
111
135
|
option = None
|
|
112
136
|
argv = sys.argv[1:]
|
|
113
137
|
for arg in argv:
|
|
114
|
-
|
|
138
|
+
if re.match(r'^-(h|-help)$', arg, re.I): self.show_usage('dsquasar')
|
|
139
|
+
ms = re.match(r'^-(a|b|c|d|e|E|l|m|n|t|u|w|A|B|D|W)$', arg)
|
|
115
140
|
if ms:
|
|
116
141
|
arg = ms.group(1)
|
|
117
142
|
if arg == 'b':
|
|
@@ -119,7 +144,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
119
144
|
elif arg in self.sopts:
|
|
120
145
|
self.sopts[arg] = 1
|
|
121
146
|
option = None
|
|
122
|
-
elif '
|
|
147
|
+
elif 'AcdlmtwW'.find(arg) > -1:
|
|
123
148
|
option = arg
|
|
124
149
|
if arg == 'd': self.bopts = []
|
|
125
150
|
elif 'BD'.find(arg) > -1:
|
|
@@ -145,36 +170,28 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
145
170
|
if not (arg == 'Y' or arg == 'N'): self.pglog(arg +": Lock Flag(-l) must be Y or N", self.LGWNEX)
|
|
146
171
|
self.PGBACK['dolock'] = 1 if arg == 'Y' else 0
|
|
147
172
|
option = None
|
|
173
|
+
elif option == 'W':
|
|
174
|
+
self.PGBACK['maxworkers'] = int(arg)
|
|
175
|
+
if self.PGBACK['maxworkers'] < 1: self.pglog(arg +": Maximum Workers(-W) must be > 0", self.LGWNEX)
|
|
176
|
+
option = None
|
|
177
|
+
elif option == 'w':
|
|
178
|
+
self.PGBACK['worker'] = int(arg)
|
|
179
|
+
if self.PGBACK['worker'] < 1: self.pglog(arg +": Worker Slot(-w) must be > 0", self.LGWNEX)
|
|
180
|
+
option = None
|
|
148
181
|
else:
|
|
149
182
|
self.add_to_dsids(arg)
|
|
150
183
|
else:
|
|
151
184
|
self.pglog(arg + ": Value without leading option", self.LGWNEX)
|
|
152
|
-
if not (self.sopts['a'] or self.dsids):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
print(" Option -t - specify dataset IDs to backup files, wildcard % is allowed")
|
|
157
|
-
print(" Option -b - turn the background mode on; no screen display")
|
|
158
|
-
print(" Option -c - change days to work with option -A 1 to rebackup changed files")
|
|
159
|
-
print(" Option -d - turn the delay mode on for running as a batch job")
|
|
160
|
-
print(" Option -e - send an email to specialist for statistics")
|
|
161
|
-
print(" Option -E - send an email to specialist for detail statistics")
|
|
162
|
-
print(" Option -B - Quasar Backup only for the provided datasets, or")
|
|
163
|
-
print(" Option -D - Quasar Backup&Drdata; default to both B & D")
|
|
164
|
-
print(" Option -A - Action bits, 1 - create dsarch input files; 2 - build tarfiles;")
|
|
165
|
-
print(" 4 - Transfer Quasar backup files; 8 - Check Backup Files;")
|
|
166
|
-
print(" 16 - Dump Backup Statistics; default to 3 for 1+2 actions,")
|
|
167
|
-
print(" and the valid values are 1,2,3,4,6,7,8,16")
|
|
168
|
-
print(" Option -l - flag to check&lock dataset before backup processes, defaults to Y")
|
|
169
|
-
print(" Option -m - number of concurrent processes, defaults to 1")
|
|
170
|
-
print(" Option -n - gather and show the available file counts with no backup actions")
|
|
171
|
-
print(" Option -u - to clean up backup locks for provided dataset IDs")
|
|
172
|
-
sys.exit(0)
|
|
185
|
+
if not (self.sopts['a'] or self.dsids): self.show_usage('dsquasar')
|
|
186
|
+
# an even worker slot walks the datasets from the other end so that it and the odd
|
|
187
|
+
# slot before it meet in the middle instead of fighting over the same dataset locks
|
|
188
|
+
if self.reverse_dsids(): self.dsids.reverse()
|
|
173
189
|
self.PGBACK['cmd'] = "dsquasar {}".format(' '.join(argv))
|
|
174
190
|
|
|
175
191
|
# do quasar backup process
|
|
176
192
|
def start_actions(self):
|
|
177
193
|
self.cmdlog(self.PGBACK['cmd'])
|
|
194
|
+
self.PGBACK['starttime'] = tm()
|
|
178
195
|
if self.sopts['u']:
|
|
179
196
|
if self.sopts['a']: self.pglog("-u: Dataset IDs must be provided to Unlock datasets", self.LOGWRN)
|
|
180
197
|
self.unlock_datasets()
|
|
@@ -331,21 +348,148 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
331
348
|
self.backup_dataset_tarfiles(dsfiles, 'B')
|
|
332
349
|
self.backup_dataset_tarfiles(dsfiles, 'D')
|
|
333
350
|
|
|
351
|
+
# size up the number of batch processes to reserve, sizing tarring and uploading
|
|
352
|
+
# independently because their parallel work units differ. tarring's unit is the tar
|
|
353
|
+
# file (one child per tar): new infiles CINACT will create this run (estimated from
|
|
354
|
+
# the total bytes of GDEX files ready to back up, ~one tar per TARSIZE) plus existing
|
|
355
|
+
# status 'N' infile records to build (TARACT), scaled one process per MPTLIMIT tar
|
|
356
|
+
# files, capped at MPTMAX. uploading's unit is the BCKSIZE transfer batch (one child
|
|
357
|
+
# per ~BCKSIZE/TARSIZE tar files): existing status 'T' tar records to transfer
|
|
358
|
+
# (BCKACT), scaled one process per MPBLIMIT tar files, capped at MPBMAX. the phases
|
|
359
|
+
# run sequentially in one run and share a single reserved ncpus, so return the max of
|
|
360
|
+
# the two counts. create-infile only (-A 1) is not a batch action (see NBACTS) so it
|
|
361
|
+
# never reaches here and stays single process.
|
|
362
|
+
def batch_process_count(self, acts):
|
|
363
|
+
mproc = 1
|
|
364
|
+
tcnt = 0
|
|
365
|
+
if acts&self.CINACT:
|
|
366
|
+
sizes = [0]
|
|
367
|
+
if self.gather_dataset_files(None, False, sizes):
|
|
368
|
+
tcnt += max(1, (sizes[0] + self.TARSIZE - 1)//self.TARSIZE)
|
|
369
|
+
if acts&self.TARACT: tcnt += self.batch_tar_count('N')
|
|
370
|
+
if tcnt > self.MPTLIMIT:
|
|
371
|
+
tproc = (tcnt + self.MPTLIMIT - 1)//self.MPTLIMIT
|
|
372
|
+
if tproc > self.MPTMAX: tproc = self.MPTMAX
|
|
373
|
+
if tproc > mproc: mproc = tproc
|
|
374
|
+
if acts&self.BCKACT:
|
|
375
|
+
bcnt = self.batch_tar_count('T')
|
|
376
|
+
if bcnt > self.MPBLIMIT:
|
|
377
|
+
bproc = (bcnt + self.MPBLIMIT - 1)//self.MPBLIMIT
|
|
378
|
+
if bproc > self.MPBMAX: bproc = self.MPBMAX
|
|
379
|
+
if bproc > mproc: mproc = bproc
|
|
380
|
+
return mproc
|
|
381
|
+
|
|
382
|
+
# count the bfile tar records of a given status honoring backflag/dataset scope
|
|
383
|
+
def batch_tar_count(self, status):
|
|
384
|
+
bcnd = "status = '{}'".format(status)
|
|
385
|
+
if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
|
|
386
|
+
if self.dsids:
|
|
387
|
+
tcnt = 0
|
|
388
|
+
for dsid in self.dsids:
|
|
389
|
+
tcnt += self.pgget('bfile', '', "dsid = '{}' AND {}".format(dsid, bcnd), self.LGWNEX)
|
|
390
|
+
return tcnt
|
|
391
|
+
return self.pgget('bfile', '', bcnd, self.LGWNEX)
|
|
392
|
+
|
|
393
|
+
# look up the dscheck record registered for a worker's argv, using the same lookup key
|
|
394
|
+
# init_dscheck builds
|
|
395
|
+
def lookup_worker_dscheck(self, argv):
|
|
396
|
+
dargv = self.argv_to_string(argv, 0, "Process in Delayed Mode")
|
|
397
|
+
dargx = None
|
|
398
|
+
if len(dargv) > 100:
|
|
399
|
+
dargx = dargv[100:]
|
|
400
|
+
dargv = dargv[0:100]
|
|
401
|
+
return self.get_dscheck("dsquasar", dargv, self.PGBACK['workdir'], self.PGLOG['CURUID'], dargx, self.LOGWRN)
|
|
402
|
+
|
|
403
|
+
# a running batch job counts as stalled once it has run longer than WORKGRACE and has
|
|
404
|
+
# still completed no more than MINWDONE of its recorded work; anything further along
|
|
405
|
+
# than that is progressing and is left alone. the done fraction dcount/fcount is used
|
|
406
|
+
# instead of a raw count because dcount counts GDEX files for -A 3 but tar files for
|
|
407
|
+
# -A 2/-A 4. a job that is not locked, not started yet, or whose process is gone is not
|
|
408
|
+
# stalled: init_dscheck already restarts a dead one.
|
|
409
|
+
def worker_progress_stalled(self, pgrec):
|
|
410
|
+
if not (pgrec['pid'] and pgrec['stttime']): return False
|
|
411
|
+
if self.check_host_pid(pgrec['lockhost'], pgrec['pid']) <= 0: return False
|
|
412
|
+
if (tm() - pgrec['stttime']) < self.WORKGRACE: return False
|
|
413
|
+
done = pgrec['dcount']/pgrec['fcount'] if pgrec['fcount'] else 0
|
|
414
|
+
return done <= self.MINWDONE
|
|
415
|
+
|
|
416
|
+
# whether more than one worker may run the current action. two workers stay off each
|
|
417
|
+
# other's files only through dataset locking, so the action must lock what it works on:
|
|
418
|
+
# creating input files and building tar files do (backup_dataset_files and
|
|
419
|
+
# backup_dataset_infiles lock each dsid), but transferring (BCKACT) walks the status 'T'
|
|
420
|
+
# records by bid without locking, so two workers would transfer the same tar files twice.
|
|
421
|
+
def extra_workers_allowed(self):
|
|
422
|
+
return bool(self.PGBACK['dolock'] and not self.PGBACK['action']&self.BCKACT)
|
|
423
|
+
|
|
424
|
+
# pick the worker slot to submit into. slot 1 uses the invocation argv unchanged so a
|
|
425
|
+
# repeat submit still blocks as a duplicate. when a slot is held by a running job that
|
|
426
|
+
# is stalled, the next slot is tried instead, and appending -w N to the argv gives that
|
|
427
|
+
# extra worker its own dscheck record and PBS job. returns the slot and the dscheck
|
|
428
|
+
# record already registered for it, if any.
|
|
429
|
+
def pick_worker_slot(self):
|
|
430
|
+
if self.PGBACK['worker'] > 1: # an explicit -w N forces that slot
|
|
431
|
+
return (self.PGBACK['worker'], self.lookup_worker_dscheck(sys.argv[1:]))
|
|
432
|
+
pgrec = self.lookup_worker_dscheck(sys.argv[1:])
|
|
433
|
+
if not pgrec or not self.worker_progress_stalled(pgrec): return (1, pgrec)
|
|
434
|
+
# worker 1 is running but stalled: submit an extra worker into the first slot with no
|
|
435
|
+
# dscheck record yet. a slot that is already registered is left alone, so an extra
|
|
436
|
+
# worker still waiting to be started by the dscheck daemon is never taken over and
|
|
437
|
+
# run here instead, outside PBS.
|
|
438
|
+
maxw = self.PGBACK['maxworkers'] if self.extra_workers_allowed() else 1
|
|
439
|
+
for slot in range(2, maxw + 1):
|
|
440
|
+
if not self.lookup_worker_dscheck(sys.argv[1:] + ['-w', str(slot)]):
|
|
441
|
+
self.pglog("Worker 1 is running with {} of {} done: adding worker {}".format(
|
|
442
|
+
pgrec['dcount'], pgrec['fcount'], slot), self.LOGWRN)
|
|
443
|
+
return (slot, None)
|
|
444
|
+
return (1, pgrec) # no free slot; block on worker 1 as usual
|
|
445
|
+
|
|
334
446
|
# start none daemon and intialize dscheck counts
|
|
335
447
|
def start_dsquasar_none_daemon(self, act, fcnt = 0):
|
|
336
448
|
acts = self.PGBACK['action']
|
|
449
|
+
cact = ('A' if acts < 10 else '') + str(acts)
|
|
337
450
|
if self.bopts != None:
|
|
338
451
|
dsid = self.dsids[0] if len(self.dsids) == 1 else ''
|
|
339
452
|
if not re.match(r'^[a-z]\d{6}$', dsid): dsid = ''
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
453
|
+
# only the initial delayed submit (not yet under PBS) sets qoptions for the batch
|
|
454
|
+
# job the dscheck daemon will start; size up multi-processing from the detail file
|
|
455
|
+
# count (unless an explicit -m N was given) and reserve matching PBS cpus. a
|
|
456
|
+
# command-line or the PBS batch run does not set qoptions.
|
|
457
|
+
if self.PGLOG['CURBID'] < 1 and (act == self.STTACT or acts&self.NBACTS != acts):
|
|
458
|
+
# pick the worker slot first; slot 1 keeps the invocation argv so a repeat
|
|
459
|
+
# submit still blocks as a duplicate, while a stalled run hands the next slot
|
|
460
|
+
# its own -w N argv. only size up multi-processing - which checks the backup
|
|
461
|
+
# files - when the chosen slot has no dscheck record on file yet, so a
|
|
462
|
+
# duplicate submit blocks without checking the backup files.
|
|
463
|
+
(slot, pgrec) = self.pick_worker_slot()
|
|
464
|
+
if slot > 1:
|
|
465
|
+
self.PGBACK['worker'] = slot
|
|
466
|
+
sys.argv.extend(['-w', str(slot)])
|
|
467
|
+
self.PGBACK['cmd'] += " -w {}".format(slot)
|
|
468
|
+
if not pgrec:
|
|
469
|
+
qoptions = '-l walltime=24:00:00'
|
|
470
|
+
if acts&self.NBACTS != acts:
|
|
471
|
+
if self.PGBACK['mproc'] < 2: self.PGBACK['mproc'] = self.batch_process_count(acts)
|
|
472
|
+
if self.PGBACK['mproc'] > 1:
|
|
473
|
+
qoptions += ",select=1:ncpus={0}:mem={0}gb".format(self.PGBACK['mproc'])
|
|
474
|
+
self.set_one_boption('qoptions', qoptions, 1)
|
|
343
475
|
self.init_dscheck(0, '', "dsquasar", dsid, cact, self.PGBACK['workdir'],
|
|
344
476
|
self.PGLOG['CURUID'], self.bopts, self.LOGWRN)
|
|
345
|
-
|
|
477
|
+
# on the PBS batch run, take the process count from the reserved ncpus in qoptions
|
|
478
|
+
# instead of recomputing it
|
|
479
|
+
if self.PGLOG['DSCHECK'] and self.PGBACK['mproc'] < 2 and acts&self.NBACTS != acts:
|
|
480
|
+
pgrec = self.pgget('dscheck', 'qoptions',
|
|
481
|
+
"cindex = {}".format(self.PGLOG['DSCHECK']['cindex']), self.LGEREX)
|
|
482
|
+
if pgrec and pgrec['qoptions']:
|
|
483
|
+
ms = re.search(r'ncpus=(\d+)', pgrec['qoptions'])
|
|
484
|
+
if ms: self.PGBACK['mproc'] = int(ms.group(1))
|
|
485
|
+
if self.PGBACK['mproc'] > 1 and acts&self.NBACTS == acts: self.PGBACK['mproc'] = 1
|
|
346
486
|
elif self.PGBACK['mproc'] > 1 and acts&self.NBACTS == acts:
|
|
347
487
|
self.PGBACK['mproc'] = 1
|
|
348
|
-
|
|
488
|
+
# tag the daemon string with the action, and with the worker slot for an extra worker,
|
|
489
|
+
# so that concurrent workers can be told apart in the log
|
|
490
|
+
dact = cact
|
|
491
|
+
if self.PGBACK['worker'] > 1: dact += "-w{}".format(self.PGBACK['worker'])
|
|
492
|
+
self.start_none_daemon('dsquasar', dact, self.PGLOG['CURUID'], self.PGBACK['mproc'], 60, 1)
|
|
349
493
|
if self.PGLOG['DSCHECK']:
|
|
350
494
|
if act == self.STTACT:
|
|
351
495
|
fcnt = self.gather_dataset_bckfiles(None, False)
|
|
@@ -409,13 +553,17 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
409
553
|
dmsg = list(bfiles)[0] if dcnt == 1 else "{} datasets".format(dcnt)
|
|
410
554
|
self.pglog("{}: {} for {} ...".format(amsg, bmsg, dmsg), self.WARNLG)
|
|
411
555
|
qinfo = {'backflag' : backflag, 'bid' : self.current_bid(), 'dsids' : [], 'fcnt' : 0,
|
|
412
|
-
'size' : 0, 'infiles' : [], 'instr' : '', 'qdsids' : [],
|
|
556
|
+
'size' : 0, 'infiles' : [], 'instr' : '', 'qdsids' : [], 'abids' : [],
|
|
413
557
|
'dslocks' : [], 'qfcnt' : 0, 'qsize' : 0, 'qcnt' : 0}
|
|
414
558
|
for dsid in bfiles:
|
|
415
559
|
if len(qinfo['dsids']) == self.DSCNT: self.process_one_backup_file(qinfo, True, False)
|
|
416
560
|
if self.PGBACK['dolock'] and dsid not in qinfo['dslocks']:
|
|
417
561
|
if self.lock_dataset(dsid, 1, self.LOGERR) < 1: continue
|
|
418
562
|
qinfo['dslocks'].append(dsid)
|
|
563
|
+
if not self.regather_dataset_files(bfiles, dsid, backflag):
|
|
564
|
+
self.lock_dataset(dsid, 0, self.LGEREX)
|
|
565
|
+
qinfo['dslocks'].remove(dsid)
|
|
566
|
+
continue
|
|
419
567
|
fcnt = bfiles[dsid]['scount']
|
|
420
568
|
if fcnt > 0:
|
|
421
569
|
self.process_backup_files(qinfo, dsid, fcnt, bfiles[dsid]['srecs'], 'S')
|
|
@@ -427,6 +575,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
427
575
|
self.lock_dataset(dsid, 0, self.LGEREX)
|
|
428
576
|
qinfo['dslocks'].remove(dsid)
|
|
429
577
|
qinfo['dsids'] = []
|
|
578
|
+
if self.PGBACK['mproc'] > 1:
|
|
579
|
+
self.check_child(None, 0, self.LOGWRN, 1) # wait all child processes done
|
|
580
|
+
self.confirm_quasar_counts(qinfo, qinfo['abids'], "status = 'T'") # recount confirmed backups from RDADB
|
|
430
581
|
if qinfo['dslocks']:
|
|
431
582
|
for dsid in qinfo['dslocks']:
|
|
432
583
|
self.lock_dataset(dsid, 0, self.LGEREX) # unlock the locked datasets
|
|
@@ -453,6 +604,44 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
453
604
|
msg = "{}: {}({}) GDEX {}file{} of {} for next {}".format(amsg, fcnt, ssize, cmsg, s, dmsg, bmsg)
|
|
454
605
|
self.pglog(self.INDENT + msg, self.LOGACT)
|
|
455
606
|
|
|
607
|
+
# the file list was gathered before this dataset was locked, so it can be hours old and
|
|
608
|
+
# name files another worker has backed up since. re-gather it now that the dataset is
|
|
609
|
+
# locked and no other worker can add backup files for it, and replace the stale list.
|
|
610
|
+
# returns the file count left to back up for the given backup flag.
|
|
611
|
+
def regather_dataset_files(self, bfiles, dsid, backflag):
|
|
612
|
+
pgrec = self.pgget("dataset", "backflag", "dsid = '{}'".format(dsid), self.LGWNEX)
|
|
613
|
+
if not pgrec: return 0
|
|
614
|
+
dsfiles = {'B' : {}, 'D' : {}}
|
|
615
|
+
self.get_dataset_files(dsid, dsfiles, pgrec['backflag'])
|
|
616
|
+
if dsid not in dsfiles[backflag]:
|
|
617
|
+
self.pglog("{}: no {} file left to back up, skip".format(dsid, self.BACKMSG[backflag]), self.DTLACT)
|
|
618
|
+
return 0
|
|
619
|
+
bfiles[dsid] = dsfiles[backflag][dsid]
|
|
620
|
+
return bfiles[dsid]['scount'] + bfiles[dsid]['wcount']
|
|
621
|
+
|
|
622
|
+
# lock the other datasets whose files share one tar file, so that no other worker gathers
|
|
623
|
+
# or tars their files while this tar file is being claimed. datasets already locked by this
|
|
624
|
+
# run, including the primary one, are left alone. returns the datasets locked here, to be
|
|
625
|
+
# unlocked once the tar file is dispatched, or None if one of them is held by another
|
|
626
|
+
# worker, in which case nothing new is left locked and the tar file must wait for a later run
|
|
627
|
+
def lock_backup_datasets(self, qinfo, dsids):
|
|
628
|
+
dslocks = []
|
|
629
|
+
if not self.PGBACK['dolock']: return dslocks
|
|
630
|
+
for dsid in dsids:
|
|
631
|
+
if dsid in qinfo['dslocks']: continue
|
|
632
|
+
if self.lock_dataset(dsid, 1, self.LOGERR) < 1:
|
|
633
|
+
self.unlock_backup_datasets(qinfo, dslocks)
|
|
634
|
+
return None
|
|
635
|
+
qinfo['dslocks'].append(dsid)
|
|
636
|
+
dslocks.append(dsid)
|
|
637
|
+
return dslocks
|
|
638
|
+
|
|
639
|
+
# unlock the given datasets and drop them from the lock list tracked for cleanup on quit
|
|
640
|
+
def unlock_backup_datasets(self, qinfo, dslocks):
|
|
641
|
+
for dsid in dslocks:
|
|
642
|
+
self.lock_dataset(dsid, 0, self.LOGERR)
|
|
643
|
+
if dsid in qinfo['dslocks']: qinfo['dslocks'].remove(dsid)
|
|
644
|
+
|
|
456
645
|
# get the current bid for adding bfile
|
|
457
646
|
def current_bid(self):
|
|
458
647
|
pgrec = self.pgget("bfile", "max(bid) mid", '', self.LGEREX)
|
|
@@ -475,16 +664,36 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
475
664
|
bmsg = self.BACKMSG[backflag]
|
|
476
665
|
s = 's' if bcnt > 1 else ''
|
|
477
666
|
self.pglog("{}: {} {} file{}...".format(amsg, bcnt, bmsg, s), self.WARNLG)
|
|
478
|
-
qinfo = {'backflag' : backflag, 'bid' : 0, 'dsids' : [], 'fcnt' : 0, 'size' : 0,
|
|
479
|
-
'infiles' : [], 'instr' : '', 'qdsids' : [], '
|
|
667
|
+
qinfo = {'backflag' : backflag, 'bid' : 0, 'dsids' : [], 'fcnt' : 0, 'size' : 0, 'abids' : [],
|
|
668
|
+
'infiles' : [], 'instr' : '', 'qdsids' : [], 'dslocks' : [], 'qfcnt' : 0,
|
|
669
|
+
'qsize' : 0, 'qcnt' : 0}
|
|
480
670
|
for dsid in bfiles:
|
|
481
|
-
if self.PGBACK['dolock']
|
|
671
|
+
if self.PGBACK['dolock']:
|
|
672
|
+
if self.lock_dataset(dsid, 1, self.LOGERR) < 1: continue
|
|
673
|
+
qinfo['dslocks'].append(dsid)
|
|
482
674
|
for bid in bfiles[dsid]:
|
|
675
|
+
# the status 'N' records were gathered before this dataset was locked, so they
|
|
676
|
+
# can be hours old; confirm each one is still untarred before spending a tar on
|
|
677
|
+
# it. holding the dataset lock makes this check race free against a second
|
|
678
|
+
# worker, which cannot hold the same dsid at the same time.
|
|
679
|
+
if not self.pgget('bfile', '', "bid = {} AND status = 'N'".format(bid), self.LGEREX):
|
|
680
|
+
self.pglog("{}-{}: no longer waiting to be tarred, skip".format(dsid, bid), self.DTLACT)
|
|
681
|
+
continue
|
|
483
682
|
qinfo['bid'] = bid
|
|
484
683
|
binfo = bfiles[dsid][bid]
|
|
485
684
|
for bkey in binfo: qinfo[bkey] = binfo[bkey]
|
|
685
|
+
# a tar file can hold files of multiple datasets; lock the other ones too before
|
|
686
|
+
# tarring, and release them again as soon as the tar file is dispatched
|
|
687
|
+
dslocks = self.lock_backup_datasets(qinfo, binfo['dsids'])
|
|
688
|
+
if dslocks is None:
|
|
689
|
+
self.pglog("{}-{}: another dataset of the tar file is locked, skip".format(dsid, bid), self.DTLACT)
|
|
690
|
+
continue
|
|
486
691
|
self.process_one_backup_file(qinfo, False)
|
|
487
|
-
|
|
692
|
+
self.unlock_backup_datasets(qinfo, dslocks)
|
|
693
|
+
if self.PGBACK['dolock']: self.unlock_backup_datasets(qinfo, [dsid])
|
|
694
|
+
if self.PGBACK['mproc'] > 1:
|
|
695
|
+
self.check_child(None, 0, self.LOGWRN, 1) # wait all child processes done
|
|
696
|
+
self.confirm_quasar_counts(qinfo, qinfo['abids'], "status = 'T'") # recount confirmed backups from RDADB
|
|
488
697
|
qcnt = qinfo['qcnt']
|
|
489
698
|
if qcnt > 0:
|
|
490
699
|
self.PGBACK['bckcnt'] += qcnt
|
|
@@ -522,6 +731,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
522
731
|
if qinfo['size'] < self.BCKSIZE: continue
|
|
523
732
|
self.transfer_quasar_tarfiles(qinfo)
|
|
524
733
|
if qinfo['size'] > 0: self.transfer_quasar_tarfiles(qinfo)
|
|
734
|
+
if self.PGBACK['mproc'] > 1:
|
|
735
|
+
self.check_child(None, 0, self.LOGWRN, 1) # wait all child processes done
|
|
736
|
+
self.confirm_quasar_counts(qinfo, list(bfiles), "status = 'A'") # recount confirmed backups from RDADB
|
|
525
737
|
qcnt = qinfo['qcnt']
|
|
526
738
|
if qcnt > 0:
|
|
527
739
|
self.PGBACK['bckcnt'] += qcnt
|
|
@@ -562,11 +774,79 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
562
774
|
if self.PGBACK['pstep']: self.record_dscheck_status("F")
|
|
563
775
|
self.pgexit(0)
|
|
564
776
|
|
|
777
|
+
# guard the -A 3 (Create Input&Tar, build status 'N' infiles) and -A 4 (Transfer,
|
|
778
|
+
# send status 'T' tars) PBS batch jobs against the 24-hour walltime: once past
|
|
779
|
+
# MAXRUNTIME, if the tar files still to process cannot finish within the last hour at
|
|
780
|
+
# the rate achieved so far, send a progress email and stop cleanly so the report is
|
|
781
|
+
# not lost to a PBS timeout; the remaining records are left for the next scheduled run
|
|
782
|
+
# to resume. a no-op for command-line runs, other actions, or before the cutoff.
|
|
783
|
+
def check_batch_deadline(self, qinfo):
|
|
784
|
+
act = self.PGBACK['action']
|
|
785
|
+
if self.PGLOG['CURBID'] < 1 or act not in (self.CTACTS, self.BCKACT): return
|
|
786
|
+
elapsed = tm() - self.PGBACK['starttime']
|
|
787
|
+
if elapsed < self.MAXRUNTIME: return
|
|
788
|
+
status, work = ('N', 'build') if act == self.CTACTS else ('T', 'transfer')
|
|
789
|
+
remaining = self.batch_tar_count(status)
|
|
790
|
+
if remaining < 1: return
|
|
791
|
+
done = self.PGBACK['tardone']
|
|
792
|
+
need = remaining*elapsed/done if done > 0 else elapsed
|
|
793
|
+
if need <= self.ONEHOUR: return # enough time left to finish before the walltime
|
|
794
|
+
if self.PGBACK['mproc'] > 1: self.check_child(None, 0, self.LOGWRN, 1) # wait all children
|
|
795
|
+
if qinfo and qinfo.get('dslocks'):
|
|
796
|
+
for dsid in qinfo['dslocks']: self.lock_dataset(dsid, 0, self.LGEREX)
|
|
797
|
+
etime = self.seconds_to_string_time(int(elapsed))
|
|
798
|
+
amsg = self.ACTMSG[act]
|
|
799
|
+
msg = "{}: Stopped after {} with {} tar file(s) still to {} - not enough time to finish before the 24-hour PBS walltime; the next scheduled run will resume".format(amsg, etime, remaining, work)
|
|
800
|
+
self.pglog(self.INDENT + msg, self.LOGACT)
|
|
801
|
+
if self.PGBACK['doemail']:
|
|
802
|
+
bmsg = self.BACKMSG[self.PGBACK['backflag']] if self.PGBACK['backflag'] else 'backup'
|
|
803
|
+
self.set_email("{}: {} - stopped early before the PBS walltime with {} {} tar file(s) remaining!".format(self.PGBACK['cmd'], amsg, remaining, bmsg), self.EMLTOP)
|
|
804
|
+
title = "dsquasar: {} stopped early ({} remaining)".format(amsg, remaining)
|
|
805
|
+
if self.PGBACK['errcnt']: title += " Error({})".format(self.PGBACK['errcnt'])
|
|
806
|
+
if self.PGLOG['DSCHECK']:
|
|
807
|
+
self.build_customized_email("dscheck", "einfo", "cindex = {}".format(self.PGLOG['DSCHECK']['cindex']), title, self.LOGWRN)
|
|
808
|
+
else:
|
|
809
|
+
self.pglog(title, self.LOGWRN|self.SNDEML)
|
|
810
|
+
if self.PGBACK['pstep']: self.record_dscheck_status("D")
|
|
811
|
+
self.pgexit(0)
|
|
812
|
+
|
|
813
|
+
# recompute the confirmed backup counts from RDADB after all child processes
|
|
814
|
+
# finished, so a multi-process summary reflects succeeded (not just started) work
|
|
815
|
+
def confirm_quasar_counts(self, qinfo, bids, dcnd):
|
|
816
|
+
qinfo['qcnt'] = qinfo['qfcnt'] = qinfo['qsize'] = 0
|
|
817
|
+
qinfo['qdsids'] = []
|
|
818
|
+
if not bids: return
|
|
819
|
+
cnd = "{} AND bid IN ({})".format(dcnd, ','.join(str(bid) for bid in bids))
|
|
820
|
+
pgrecs = self.pgmget('bfile', "dsid, dsids, data_size size, (scount + wcount) fcnt", cnd, self.LGEREX)
|
|
821
|
+
bcnt = len(pgrecs['dsid']) if pgrecs else 0
|
|
822
|
+
for i in range(bcnt):
|
|
823
|
+
qinfo['qcnt'] += 1
|
|
824
|
+
qinfo['qfcnt'] += pgrecs['fcnt'][i]
|
|
825
|
+
qinfo['qsize'] += pgrecs['size'][i]
|
|
826
|
+
dsid = pgrecs['dsid'][i]
|
|
827
|
+
if dsid not in qinfo['qdsids']: qinfo['qdsids'].append(dsid)
|
|
828
|
+
if pgrecs['dsids'][i]:
|
|
829
|
+
for did in pgrecs['dsids'][i].split(','):
|
|
830
|
+
if did and did not in qinfo['qdsids']: qinfo['qdsids'].append(did)
|
|
831
|
+
|
|
832
|
+
# dsarch reports "<dsid>-<file>: <type> file backed up to /<dsid>/<bfile> by <date>" for
|
|
833
|
+
# each file to tar that is already backed up. our own record is the duplicate to drop only
|
|
834
|
+
# when those files point at some other bfile; a report naming our own bfile means this
|
|
835
|
+
# record is the one already backed up, so it must be kept.
|
|
836
|
+
def duplicate_backup_file(self, dsid, qfile):
|
|
837
|
+
ours = "/{}/{}".format(dsid, qfile)
|
|
838
|
+
isdup = False
|
|
839
|
+
for ms in re.finditer(r'file backed up to (\S+) by ', self.PGLOG['SYSERR']):
|
|
840
|
+
if ms.group(1) == ours: return False
|
|
841
|
+
isdup = True
|
|
842
|
+
return isdup
|
|
843
|
+
|
|
565
844
|
# backup one Quasar Backup or Backup&Drdata from one or multiple inputs and,
|
|
566
845
|
# reset the quasar backup dict
|
|
567
846
|
def process_one_backup_file(self, qinfo, addback, keepid = False):
|
|
568
847
|
ccnt = self.check_child(None, 0, self.LOGWRN, -1) if self.PGBACK['mproc'] > 1 else 0
|
|
569
848
|
if self.PGSIG['QUIT']: self.quit_dsquasar(qinfo)
|
|
849
|
+
self.check_batch_deadline(qinfo)
|
|
570
850
|
dsids = qinfo['dsids']
|
|
571
851
|
dcnt = len(dsids)
|
|
572
852
|
if dcnt == 0: return
|
|
@@ -580,6 +860,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
580
860
|
else:
|
|
581
861
|
bid = qinfo['bid']
|
|
582
862
|
qfile = qinfo['bfile']
|
|
863
|
+
if 'abids' in qinfo: qinfo['abids'].append(bid) # track dispatched bids for confirmed recount
|
|
583
864
|
stat = 1
|
|
584
865
|
if self.PGBACK['action'] > self.CINACT:
|
|
585
866
|
s = 's' if fcnt > 1 else ''
|
|
@@ -599,9 +880,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
599
880
|
stat = self.pgsystem(cmd, self.ERRACT, 325) # 256 + 64 + 4 + 1
|
|
600
881
|
if stat:
|
|
601
882
|
for infile in qinfo['infiles']: self.delete_local_file(infile)
|
|
602
|
-
elif
|
|
883
|
+
elif self.duplicate_backup_file(dsid, qfile):
|
|
603
884
|
if self.pgdel('bfile', f"bid = {bid}"):
|
|
604
|
-
self.pglog(f"{dsid}-{qfile}: backup tarfile deleted for
|
|
885
|
+
self.pglog(f"{dsid}-{qfile}: backup tarfile deleted for duplication", self.DTLACT)
|
|
605
886
|
for infile in qinfo['infiles']: self.delete_local_file(infile)
|
|
606
887
|
sys.exit(0) # stop child process
|
|
607
888
|
else:
|
|
@@ -612,9 +893,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
612
893
|
stat = self.pgsystem(cmd, self.ERRACT, 325) # 256 + 64 + 4 + 1
|
|
613
894
|
if stat:
|
|
614
895
|
for infile in qinfo['infiles']: self.delete_local_file(infile)
|
|
615
|
-
elif
|
|
896
|
+
elif self.duplicate_backup_file(dsid, qfile):
|
|
616
897
|
if self.pgdel('bfile', f"bid = {bid}"):
|
|
617
|
-
self.pglog(f"{dsid}-{qfile}: backup tarfile deleted for
|
|
898
|
+
self.pglog(f"{dsid}-{qfile}: backup tarfile deleted for duplication", self.DTLACT)
|
|
618
899
|
for infile in qinfo['infiles']: self.delete_local_file(infile)
|
|
619
900
|
else:
|
|
620
901
|
self.PGBACK['errcnt'] += 1
|
|
@@ -623,12 +904,13 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
623
904
|
if stat:
|
|
624
905
|
# reset qinfo after quasar backup
|
|
625
906
|
qinfo['qcnt'] += 1
|
|
907
|
+
self.PGBACK['tardone'] += 1 # cumulative dispatched tars for the walltime finish-rate
|
|
626
908
|
qinfo['qfcnt'] += fcnt
|
|
627
909
|
qinfo['qsize'] += fsize
|
|
628
910
|
for dsid in dsids:
|
|
629
911
|
if dsid not in qinfo['qdsids']: qinfo['qdsids'].append(dsid)
|
|
630
912
|
if addback:
|
|
631
|
-
lastdsid = dsids.pop() if keepid else None
|
|
913
|
+
lastdsid = dsids.pop() if keepid else None
|
|
632
914
|
for dsid in dsids: # unlock all datasets but the last one
|
|
633
915
|
if dsid in qinfo['dslocks']:
|
|
634
916
|
self.lock_dataset(dsid, 0, self.LGEREX)
|
|
@@ -645,6 +927,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
645
927
|
def transfer_quasar_tarfiles(self, qinfo):
|
|
646
928
|
ccnt = self.check_child(None, 0, self.LOGWRN, -1) if self.PGBACK['mproc'] > 1 else 0
|
|
647
929
|
if self.PGSIG['QUIT']: self.quit_dsquasar(qinfo)
|
|
930
|
+
self.check_batch_deadline(qinfo)
|
|
648
931
|
# prepare for backup one tar file
|
|
649
932
|
dsids = qinfo['dsids']
|
|
650
933
|
bids = qinfo['bids']
|
|
@@ -675,10 +958,10 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
675
958
|
dstat = bstat = -1
|
|
676
959
|
if ccnt == 0 or self.PGSIG['PPID'] > 1:
|
|
677
960
|
if bflg == 'D':
|
|
678
|
-
dstat = self.
|
|
961
|
+
dstat = self.quasar_multiple_transfer(tofiles, fromfiles, 'gdex-quasar-drdata', 'gdex-glade', self.ERRACT)
|
|
679
962
|
if not dstat: self.pglog("Error Quaser Drdata for " + fmsg, self.ERRACT|self.LOGERR)
|
|
680
|
-
bstat = self.
|
|
681
|
-
if not
|
|
963
|
+
bstat = self.quasar_multiple_transfer(tofiles, fromfiles, 'gdex-quasar', 'gdex-glade', self.ERRACT)
|
|
964
|
+
if not bstat: self.pglog("Error Quaser Backup for " + fmsg, self.ERRACT|self.LOGERR)
|
|
682
965
|
if dstat == self.FINISH: dstat = self.check_globus_finished(tofiles[0], 'gdex-quasar-drdata', self.ERRACT|self.NOWAIT)
|
|
683
966
|
if bstat == self.FINISH: bstat = self.check_globus_finished(tofiles[0], 'gdex-quasar', self.ERRACT|self.NOWAIT)
|
|
684
967
|
if dstat and bstat:
|
|
@@ -698,6 +981,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
698
981
|
if dstat and bstat:
|
|
699
982
|
# reset qinfo after quasar backup
|
|
700
983
|
qinfo['qcnt'] += bcnt
|
|
984
|
+
self.PGBACK['tardone'] += bcnt # cumulative transferred tars for the walltime finish-rate
|
|
701
985
|
qinfo['qfcnt'] += fcnt
|
|
702
986
|
qinfo['qsize'] += fsize
|
|
703
987
|
for dsid in dsids:
|
|
@@ -777,7 +1061,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
777
1061
|
return {'wcount' : 0, 'wrecs' : [], 'scount' : 0, 'srecs' : []}
|
|
778
1062
|
|
|
779
1063
|
# get all files need to be backed up for a given dataset ID
|
|
780
|
-
def get_dataset_files(self, dsid, dsfiles, backflag, logact = 0):
|
|
1064
|
+
def get_dataset_files(self, dsid, dsfiles, backflag, logact = 0, sizes = None):
|
|
781
1065
|
dcnd = "dsid = '{}'".format(dsid)
|
|
782
1066
|
fopt = self.get_backup_options(dsid, dcnd)
|
|
783
1067
|
if not fopt:
|
|
@@ -817,6 +1101,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
817
1101
|
self.pglog("{}-G{}: Found {} Files ({})".format(dsid, gidx, rcnt, rmsg), self.LOGWRN)
|
|
818
1102
|
|
|
819
1103
|
fcnt = cnts['B'][0] + cnts['D'][0]
|
|
1104
|
+
if sizes is not None: sizes[0] += cnts['B'][1] + cnts['D'][1]
|
|
820
1105
|
if dsfiles:
|
|
821
1106
|
cmsg = 'Changed ' if self.PGBACK['chgdays'] else ''
|
|
822
1107
|
for bflg in dsfiles:
|
|
@@ -836,7 +1121,6 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
836
1121
|
|
|
837
1122
|
# get web/saved files to backup for a given group condition and file option
|
|
838
1123
|
def get_group_files(self, dsid, gcnd, dcnd, bfiles, fopt, infiles, cnts):
|
|
839
|
-
rcnt = 0
|
|
840
1124
|
if self.PGBACK['chgdays'] > 0:
|
|
841
1125
|
return self.get_group_changed_files(dsid, gcnd, dcnd, bfiles, fopt, infiles, cnts)
|
|
842
1126
|
else:
|
|
@@ -982,28 +1266,37 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
982
1266
|
def get_backup_options(self, dsid, dcnd):
|
|
983
1267
|
fopt = 0
|
|
984
1268
|
bcnd = "= 0" if self.PGBACK['chgdays'] < 1 else "> 0"
|
|
985
|
-
# if self.pgget('sfile', 'sid', "{} AND bid {}".format(dcnd, bcnd), self.LGWNEX): fopt |= self.SOPT
|
|
986
1269
|
if self.pgget_wfile(dsid, 'wid', "bid " + bcnd, self.LGWNEX): fopt |= self.WOPT
|
|
987
1270
|
return fopt
|
|
988
1271
|
|
|
1272
|
+
# dataset ordering for gathering work; even worker slots start from the high end (d999999)
|
|
1273
|
+
# and odd ones from the low end, so that a pair of workers meets in the middle instead of
|
|
1274
|
+
# fighting over the same dataset locks
|
|
1275
|
+
def dsid_order(self):
|
|
1276
|
+
return " ORDER BY dsid DESC" if self.reverse_dsids() else " ORDER BY dsid"
|
|
1277
|
+
|
|
1278
|
+
# whether this worker slot walks the datasets from the high end
|
|
1279
|
+
def reverse_dsids(self):
|
|
1280
|
+
return self.PGBACK['worker']%2 == 0
|
|
1281
|
+
|
|
989
1282
|
# gather all available dataset ids to backup data files
|
|
990
|
-
def gather_dataset_files(self, dsfiles, unlock = True):
|
|
1283
|
+
def gather_dataset_files(self, dsfiles, unlock = True, sizes = None):
|
|
991
1284
|
fcnt = 0
|
|
1285
|
+
# warn per named dataset with no files; stay silent when scanning all datasets
|
|
1286
|
+
logact = self.LOGWRN if self.dsids else 0
|
|
992
1287
|
if self.dsids:
|
|
1288
|
+
pgrecs = []
|
|
993
1289
|
for dsid in self.dsids:
|
|
994
|
-
|
|
995
|
-
pgrec
|
|
996
|
-
if pgrec:
|
|
997
|
-
if unlock and pgrec['pid'] and self.lock_dataset(dsid, 0, self.LOGACT) < 1: continue
|
|
998
|
-
fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], self.LOGWRN)
|
|
1290
|
+
pgrec = self.pgget("dataset", "dsid, backflag, pid", "dsid = '{}'".format(dsid), self.LGWNEX)
|
|
1291
|
+
if pgrec: pgrecs.append(pgrec)
|
|
999
1292
|
else:
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1293
|
+
mrecs = self.pgmget("dataset", "dsid, backflag, pid", self.dsid_order().strip(), self.LGWNEX)
|
|
1294
|
+
dcnt = len(mrecs['dsid']) if mrecs else 0
|
|
1295
|
+
pgrecs = [self.onerecord(mrecs, i) for i in range(dcnt)]
|
|
1296
|
+
for pgrec in pgrecs:
|
|
1297
|
+
dsid = pgrec['dsid']
|
|
1298
|
+
if unlock and pgrec['pid'] and self.lock_dataset(dsid, 0, self.LOGACT) < 1: continue
|
|
1299
|
+
fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact, sizes)
|
|
1007
1300
|
if dsfiles:
|
|
1008
1301
|
s = 's' if fcnt > 1 else ''
|
|
1009
1302
|
bmsg = self.BACKMSG[self.PGBACK['backflag']] if self.PGBACK['backflag'] else 'backup'
|
|
@@ -1013,28 +1306,21 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1013
1306
|
|
|
1014
1307
|
# gather all available backup file records with status N to backup
|
|
1015
1308
|
def gather_dataset_infiles(self, dsfiles):
|
|
1016
|
-
icnt = fcnt =
|
|
1309
|
+
icnt = fcnt = 0
|
|
1017
1310
|
bcnd = "status = 'N'"
|
|
1018
|
-
ocnd = " ORDER BY dsid"
|
|
1019
1311
|
flds = "bid, dsid, bfile, type, data_size size, (scount + wcount) fcnt, dsids, note"
|
|
1020
1312
|
if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
|
|
1021
1313
|
if self.dsids:
|
|
1022
|
-
for dsid in self.dsids
|
|
1023
|
-
dcnd = "dsid = '{}' AND {}".format(dsid, bcnd)
|
|
1024
|
-
pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
|
|
1025
|
-
bcnt = len(pgrecs['bid']) if pgrecs else 0
|
|
1026
|
-
for i in range(bcnt):
|
|
1027
|
-
cnt = self.get_backup_infile(dsfiles, self.onerecord(pgrecs, i))
|
|
1028
|
-
if cnt:
|
|
1029
|
-
icnt +=1
|
|
1030
|
-
fcnt += cnt
|
|
1314
|
+
cnds = ["dsid = '{}' AND {}".format(dsid, bcnd) for dsid in self.dsids]
|
|
1031
1315
|
else:
|
|
1032
|
-
|
|
1316
|
+
cnds = [bcnd + self.dsid_order()]
|
|
1317
|
+
for dcnd in cnds:
|
|
1318
|
+
pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
|
|
1033
1319
|
bcnt = len(pgrecs['bid']) if pgrecs else 0
|
|
1034
1320
|
for i in range(bcnt):
|
|
1035
1321
|
cnt = self.get_backup_infile(dsfiles, self.onerecord(pgrecs, i))
|
|
1036
1322
|
if cnt:
|
|
1037
|
-
icnt +=1
|
|
1323
|
+
icnt += 1
|
|
1038
1324
|
fcnt += cnt
|
|
1039
1325
|
if dsfiles:
|
|
1040
1326
|
s = 's' if icnt > 1 else ''
|
|
@@ -1045,28 +1331,21 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1045
1331
|
# gather all available backup file records with status T to backup
|
|
1046
1332
|
#
|
|
1047
1333
|
def gather_dataset_tarfiles(self, dsfiles):
|
|
1048
|
-
tcnt = fcnt =
|
|
1334
|
+
tcnt = fcnt = 0
|
|
1049
1335
|
bcnd = "status = 'T'"
|
|
1050
|
-
ocnd = " ORDER BY dsid"
|
|
1051
1336
|
flds = "bid, dsid, bfile, type, data_size size, (scount + wcount) fcnt, note"
|
|
1052
1337
|
if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
|
|
1053
1338
|
if self.dsids:
|
|
1054
|
-
for dsid in self.dsids
|
|
1055
|
-
dcnd = "dsid = '{}' AND {}".format(dsid, bcnd)
|
|
1056
|
-
pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
|
|
1057
|
-
bcnt = len(pgrecs['bid']) if pgrecs else 0
|
|
1058
|
-
for i in range(bcnt):
|
|
1059
|
-
cnt = self.get_backup_tarfile(dsfiles, self.onerecord(pgrecs, i))
|
|
1060
|
-
if cnt:
|
|
1061
|
-
tcnt +=1
|
|
1062
|
-
fcnt += cnt
|
|
1339
|
+
cnds = ["dsid = '{}' AND {}".format(dsid, bcnd) for dsid in self.dsids]
|
|
1063
1340
|
else:
|
|
1064
|
-
|
|
1341
|
+
cnds = [bcnd + self.dsid_order()]
|
|
1342
|
+
for dcnd in cnds:
|
|
1343
|
+
pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
|
|
1065
1344
|
bcnt = len(pgrecs['bid']) if pgrecs else 0
|
|
1066
1345
|
for i in range(bcnt):
|
|
1067
1346
|
cnt = self.get_backup_tarfile(dsfiles, self.onerecord(pgrecs, i))
|
|
1068
1347
|
if cnt:
|
|
1069
|
-
tcnt +=1
|
|
1348
|
+
tcnt += 1
|
|
1070
1349
|
fcnt += cnt
|
|
1071
1350
|
s = 's' if tcnt > 1 else ''
|
|
1072
1351
|
bmsg = self.BACKMSG[self.PGBACK['backflag']] if self.PGBACK['backflag'] else 'backup'
|
|
@@ -1092,7 +1371,6 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1092
1371
|
if self.PGBACK['action']&self.CINACT: ret = fcnt
|
|
1093
1372
|
if not dsfiles: return ret
|
|
1094
1373
|
dsids = [dsid]
|
|
1095
|
-
# if not (pgrec['note'] or self.rebuild_file_note(pgrec, dsids)): return 0
|
|
1096
1374
|
if not pgrec['note']:
|
|
1097
1375
|
return self.pglog(bfile + ": Miss note for N backup file", self.ERRACT)
|
|
1098
1376
|
infiles = []
|
|
@@ -1147,7 +1425,6 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1147
1425
|
if self.PGBACK['action']&self.CINACT: ret = fcnt
|
|
1148
1426
|
if not dsfiles: return ret
|
|
1149
1427
|
dsids = [dsid]
|
|
1150
|
-
# if not (pgrec['note'] or self.rebuild_file_note(pgrec, dsids)): return 0
|
|
1151
1428
|
if not pgrec['note']:
|
|
1152
1429
|
return self.pglog(bfile + ": Miss note for T backup file", self.ERRACT)
|
|
1153
1430
|
ftype = None
|
|
@@ -1347,6 +1624,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1347
1624
|
binfo = bfiles[bid]
|
|
1348
1625
|
for bkey in binfo: qinfo[bkey] = binfo[bkey]
|
|
1349
1626
|
self.process_one_quasar_pathfile(qinfo)
|
|
1627
|
+
if self.PGBACK['mproc'] > 1:
|
|
1628
|
+
self.check_child(None, 0, self.LOGWRN, 1) # wait all child processes done
|
|
1629
|
+
self.confirm_quasar_counts(qinfo, list(bfiles), "bfile LIKE 'G%/%.tar'") # recount confirmed renames from RDADB
|
|
1350
1630
|
qcnt = qinfo['qcnt']
|
|
1351
1631
|
if qcnt > 0:
|
|
1352
1632
|
s = 's' if qcnt > 1 else ''
|
|
@@ -1671,43 +1951,6 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1671
1951
|
dsfiles[pgrec['type']][pgrec['bid']] = {'note' : pgrec['note'], 'fcnt' : pgrec['fcnt'], 'size' : pgrec['size'],
|
|
1672
1952
|
'date' : pgrec['date_modified'], 'time' : pgrec['time_modified']}
|
|
1673
1953
|
|
|
1674
|
-
# rebuild missed file note
|
|
1675
|
-
def rebuild_file_note(self, pgrec, dsids):
|
|
1676
|
-
bid = pgrec['bid']
|
|
1677
|
-
fields = 'file, dsid, type, data_size, checksum'
|
|
1678
|
-
bfiles = {dsids[0] : {'S' : [], 'W' : []}}
|
|
1679
|
-
pgrecs = self.pgmget('sfile', 's' + fields, "bid = {} ORDER BY dsid, sfile".format(bid), self.LGEREX)
|
|
1680
|
-
cnt = len(pgrecs['sfile']) if pgrecs else 0
|
|
1681
|
-
for i in range(cnt):
|
|
1682
|
-
dsid = pgrecs['dsid'][i]
|
|
1683
|
-
if dsid not in dsids:
|
|
1684
|
-
dsids.append(dsid)
|
|
1685
|
-
bfiles[dsid] = {'S' : [], 'W' : []}
|
|
1686
|
-
bfiles[dsid]['S'].append([pgrecs['sfile'][i], pgrecs['type'][i], pgrecs['data_size'][i], pgrecs['checksum'][i]])
|
|
1687
|
-
pgrecs = self.pgmget('wfile', 'w' + fields, "bid = {} ORDER BY dsid, wfile".format(bid), self.LGEREX)
|
|
1688
|
-
cnt = len(pgrecs['wfile']) if pgrecs else 0
|
|
1689
|
-
for i in range(cnt):
|
|
1690
|
-
dsid = pgrecs['dsid'][i]
|
|
1691
|
-
if dsid not in dsids:
|
|
1692
|
-
dsids.append(dsid)
|
|
1693
|
-
bfiles[dsid] = {'S' : [], 'W' : []}
|
|
1694
|
-
bfiles[dsid]['W'].append([pgrecs['wfile'][i], pgrecs['type'][i], pgrecs['data_size'][i], pgrecs['checksum'][i]])
|
|
1695
|
-
# create note
|
|
1696
|
-
note = ''
|
|
1697
|
-
for dsid in bfiles:
|
|
1698
|
-
for ftype in bfiles[dsid]:
|
|
1699
|
-
files = bfiles[dsid][ftype]
|
|
1700
|
-
if not files: continue
|
|
1701
|
-
note += "<{}_{}_{}.txt>\n".format(dsid, ftype, bid)
|
|
1702
|
-
note += "DS<=>{}\n{}F<:>{}T<:>SZ<:>MC<:>\n".format(dsid, ftype, ftype)
|
|
1703
|
-
for flist in files:
|
|
1704
|
-
note += "{}<:>{}<:>{}<:>{}<:>\n".format(flist[0], flist[1], flist[2], flist[3])
|
|
1705
|
-
if note:
|
|
1706
|
-
self.pgexec("UPDATE bfile SET note = '{}' WHERE bid = {}".format(note, bid), self.LGEREX)
|
|
1707
|
-
pgrec['note'] = note
|
|
1708
|
-
return 1
|
|
1709
|
-
return 0
|
|
1710
|
-
|
|
1711
1954
|
# dump statistics for GDEX files available to create input files
|
|
1712
1955
|
def dump_dataset_files(self, dsfiles, backflag):
|
|
1713
1956
|
bfiles = dsfiles[backflag]
|
|
@@ -1922,7 +2165,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1922
2165
|
pcnt += 1
|
|
1923
2166
|
return pcnt
|
|
1924
2167
|
|
|
1925
|
-
# main function to
|
|
2168
|
+
# main function to execute this script
|
|
1926
2169
|
def main():
|
|
1927
2170
|
object = DsQuasar()
|
|
1928
2171
|
object.read_parameters()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
|
|
2
|
+
Name: dsquasar - back up RDA/GDEX data archives onto the Quasar backup servers
|
|
3
|
+
|
|
4
|
+
Check the Backup flags set in dataset.backflag and/or dsgroup.backflag of RDADB
|
|
5
|
+
to gather Web and Saved files, tar them into larger (>= 2GB) backup files, and
|
|
6
|
+
back them up onto the NCAR GDEX Quasar Globus end points, for a Backup copy
|
|
7
|
+
(gdex-quasar) and/or a disaster-recovery Drdata copy (gdex-quasar-drdata). The
|
|
8
|
+
file tarring and transferring is performed through the utility program dsarch.
|
|
9
|
+
|
|
10
|
+
Usage: dsquasar [-b] [(-a | -t DatasetIDs)] [-c ChangeDays] [-(e|E)] \
|
|
11
|
+
[-(B|D)] [-A ActionBits] [-l (Y|N)] [-m ProcessCount] \
|
|
12
|
+
[-n] [-u] [-d [HostName] [TryCount]] [-h]
|
|
13
|
+
|
|
14
|
+
Either -a or -t must be given (usage is displayed otherwise). dsquasar runs
|
|
15
|
+
from the Quasar backup work directory; it changes there automatically.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DATASET SELECTION
|
|
19
|
+
|
|
20
|
+
-a Gather files of all datasets that have a Backup flag set.
|
|
21
|
+
|
|
22
|
+
-t DatasetIDs
|
|
23
|
+
One or more dataset IDs whose files are backed up. The SQL wildcard
|
|
24
|
+
'%' is allowed and is expanded against the dataset table. Ignored,
|
|
25
|
+
with a warning, when -a is also given.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
BACKUP SCOPE
|
|
29
|
+
|
|
30
|
+
-B Quasar Backup only (endpoint gdex-quasar) for the given datasets.
|
|
31
|
+
|
|
32
|
+
-D Quasar Backup & Drdata (gdex-quasar and gdex-quasar-drdata).
|
|
33
|
+
|
|
34
|
+
-B and -D are mutually exclusive; the default (neither given) backs up
|
|
35
|
+
to both the Backup and the Drdata endpoints.
|
|
36
|
+
|
|
37
|
+
-c ChangeDays
|
|
38
|
+
Used with -A 1 to re-back up files that changed within the given
|
|
39
|
+
number of days. Must be greater than 0.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
ACTIONS
|
|
43
|
+
|
|
44
|
+
-A ActionBits
|
|
45
|
+
Bit flags selecting the actions to perform; the valid values are
|
|
46
|
+
1, 2, 3, 4, 6, 7, 8, 16 and it defaults to 3 (actions 1 + 2):
|
|
47
|
+
|
|
48
|
+
1 - create the dsarch input Save/Web file lists and add the
|
|
49
|
+
backup file (bfile) placeholder records (status 'N').
|
|
50
|
+
2 - build tar files (each >= 5GB where possible) from the Saved/
|
|
51
|
+
Web files listed in the input files (status 'N' -> 'T').
|
|
52
|
+
4 - transfer the built tar files (batched to >= 90GB per Globus
|
|
53
|
+
transfer) to the Quasar backup end points (status 'T' -> 'A').
|
|
54
|
+
8 - check file sizes between RDADB and the Quasar servers, and
|
|
55
|
+
reset status to 'N' when they differ.
|
|
56
|
+
16 - dump backup statistics for files backed up and files that are
|
|
57
|
+
ready to be backed up.
|
|
58
|
+
|
|
59
|
+
Combined values: 3 = 1+2, 6 = 2+4, 7 = 1+2+4.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
RUN CONTROL
|
|
63
|
+
|
|
64
|
+
-b Turn on background mode; no screen display.
|
|
65
|
+
|
|
66
|
+
-d [HostName] [TryCount]
|
|
67
|
+
Turn on delay mode to run as a batch job. A dscheck record is created
|
|
68
|
+
and the dscheck daemon later submits the job to PBS. On the initial
|
|
69
|
+
submit dsquasar sizes multi-processing from the detail file count and
|
|
70
|
+
reserves matching PBS cpus (walltime + select=1:ncpus=N:mem=Ngb) in the
|
|
71
|
+
dscheck qoptions; the invocation arguments are left unchanged so a
|
|
72
|
+
repeat submit with the same arguments still blocks as a duplicate.
|
|
73
|
+
|
|
74
|
+
-l (Y|N)
|
|
75
|
+
Whether to check and lock a dataset before backing it up. Defaults
|
|
76
|
+
to Y.
|
|
77
|
+
|
|
78
|
+
-m ProcessCount
|
|
79
|
+
Number of concurrent child processes. Defaults to 1. In delay mode
|
|
80
|
+
the process count travels through the reserved PBS cpus rather than
|
|
81
|
+
the command line.
|
|
82
|
+
|
|
83
|
+
-n Gather and show the available file counts only; no backup actions are
|
|
84
|
+
performed.
|
|
85
|
+
|
|
86
|
+
-W MaxWorkers
|
|
87
|
+
Maximum number of concurrent batch workers for the same command.
|
|
88
|
+
Defaults to 2. A repeat delayed submit normally blocks while the
|
|
89
|
+
batch job is running; if that job has run for more than 6 hours and
|
|
90
|
+
has still completed no more than 1% of its recorded work, it is
|
|
91
|
+
treated as stalled and an extra worker is submitted instead, up to
|
|
92
|
+
this limit. Extra workers rely on dataset locking to stay off each
|
|
93
|
+
other's files, so this is pinned to 1 with -l N, and also for the
|
|
94
|
+
transfer action (-A 4, 6, 7), which claims tar files without locking.
|
|
95
|
+
|
|
96
|
+
-w WorkerSlot
|
|
97
|
+
Worker slot number for this run, added automatically to an extra
|
|
98
|
+
worker's command line so it gets its own dscheck record. Not
|
|
99
|
+
normally set by hand. Even slots walk the datasets in descending
|
|
100
|
+
dsid order (from d999999) and odd slots in ascending order, so that a
|
|
101
|
+
pair of workers meets in the middle instead of contending for the
|
|
102
|
+
same dataset locks.
|
|
103
|
+
|
|
104
|
+
-u Clean up (unlock) backup locks for the given dataset IDs. Dataset IDs
|
|
105
|
+
must be provided (not valid with -a).
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
EMAIL
|
|
109
|
+
|
|
110
|
+
-e Send an email to the specialist with backup statistics.
|
|
111
|
+
|
|
112
|
+
-E Send an email to the specialist with detailed backup statistics.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
MISCELLANEOUS
|
|
116
|
+
|
|
117
|
+
-h Display this help document.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
EXAMPLES
|
|
121
|
+
|
|
122
|
+
1. Back up (create input files and tar files for) all flagged datasets:
|
|
123
|
+
|
|
124
|
+
dsquasar -a -A 3
|
|
125
|
+
|
|
126
|
+
2. Transfer built tar files of one dataset to both Quasar end points:
|
|
127
|
+
|
|
128
|
+
dsquasar -t d123456 -A 4
|
|
129
|
+
|
|
130
|
+
3. Show the counts of files ready to back up for a dataset, without acting:
|
|
131
|
+
|
|
132
|
+
dsquasar -t d123456 -n
|
|
133
|
+
|
|
134
|
+
4. Dump backup statistics for all datasets by email:
|
|
135
|
+
|
|
136
|
+
dsquasar -a -A 16 -e
|
|
137
|
+
|
|
138
|
+
5. Queue a delayed PBS batch job to create input files and tar files for all
|
|
139
|
+
datasets in the background (as run from cron); multi-processing is sized
|
|
140
|
+
automatically from the file count:
|
|
141
|
+
|
|
142
|
+
dsquasar -a -A 3 -e -b -d PBS
|
|
143
|
+
|
|
144
|
+
6. Unlock backup locks left on datasets:
|
|
145
|
+
|
|
146
|
+
dsquasar -t d123456 -u
|
{rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6/src/rda_python_dsquasar.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_dsquasar
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.6
|
|
4
4
|
Summary: RDA Python package to backup and recover RDA data archives to and from GLOBUS Quasar backup server
|
|
5
5
|
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/NCAR/rda-python-dsquasar
|
{rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar.egg-info/SOURCES.txt
RENAMED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
2
3
|
README.md
|
|
3
4
|
pyproject.toml
|
|
4
5
|
src/rda_python_dsquasar/__init__.py
|
|
5
6
|
src/rda_python_dsquasar/ds_quasar.py
|
|
6
7
|
src/rda_python_dsquasar/dsquasar.py
|
|
8
|
+
src/rda_python_dsquasar/dsquasar.usg
|
|
7
9
|
src/rda_python_dsquasar/dstacc.py
|
|
8
10
|
src/rda_python_dsquasar/taccrec.py
|
|
9
11
|
src/rda_python_dsquasar/tacctar.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rda_python_dsquasar-3.0.1 → rda_python_dsquasar-3.0.6}/src/rda_python_dsquasar/ds_quasar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|