rda-python-dsquasar 3.0.23__tar.gz → 3.0.25__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.23/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.25}/PKG-INFO +1 -1
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/pyproject.toml +1 -1
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dsquasar.py +160 -42
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dsquasar.usg +15 -12
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dstacc.py +26 -1
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25/src/rda_python_dsquasar.egg-info}/PKG-INFO +1 -1
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/LICENSE +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/MANIFEST.in +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/README.md +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/setup.cfg +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/__init__.py +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/ds_quasar.py +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/taccrec.py +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/tacctar.py +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/SOURCES.txt +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/dependency_links.txt +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/entry_points.txt +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/requires.txt +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar.egg-info/top_level.txt +0 -0
- {rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/test/test_dsquasar.py +0 -0
{rda_python_dsquasar-3.0.23/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.25}/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.25
|
|
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.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dsquasar.py
RENAMED
|
@@ -83,11 +83,18 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
83
83
|
self.TARSIZE = 5*self.PGLOG['ONEGBS'] # 5GB, tarfile size limit
|
|
84
84
|
self.MINSIZE = self.PGLOG['TWOGBS'] # 2GB, minimal tarfile size
|
|
85
85
|
self.ONESIZE = 20*self.PGLOG['ONEGBS'] # 20GB, minimal file size to tar a single file
|
|
86
|
+
self.MISSLOG = 10 # files named in the log per dataset when missing from storage
|
|
87
|
+
self.MISSCNT = 0 # files of the current dataset missing from storage
|
|
86
88
|
self.TFCOUNT = 100 # if file count is greater, use self.MINSIZE for tar file
|
|
87
89
|
self.SUBLMTS = 2000 # file count limit for a sub-group
|
|
88
90
|
# tarring (TARACT) forks one child per tar file, so its parallel unit is the tar
|
|
89
91
|
# file: use a small per-process limit and a high cap to spread the work widely.
|
|
90
|
-
self.MPTLIMIT = 200 # tar file count per batch process for tarring
|
|
92
|
+
self.MPTLIMIT = 200 # tar file count per batch process for tarring on Glade
|
|
93
|
+
# a tar file of an object store dataset has every member staged down from Boreas and
|
|
94
|
+
# checksummed first, so it takes many times longer than a Glade one: give it a
|
|
95
|
+
# process per far fewer tar files. this is the only way to speed up a backlog held
|
|
96
|
+
# in one dataset, since a second worker cannot share the dataset lock.
|
|
97
|
+
self.MPOLIMIT = 20 # tar file count per batch process for tarring on the object store
|
|
91
98
|
self.MPTMAX = 12 # maximum number of batch processes for tarring
|
|
92
99
|
# uploading (BCKACT) forks one child per BCKSIZE transfer batch (~BCKSIZE/TARSIZE
|
|
93
100
|
# tar files each), so far fewer parallel units than tar files: use a large
|
|
@@ -105,8 +112,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
105
112
|
# dscheck work done, which is action independent because dcount counts GDEX files
|
|
106
113
|
# for -A 3 but tar files for -A 2/-A 4.
|
|
107
114
|
self.MAXWORKERS = 2 # default maximum concurrent workers per command (-W)
|
|
108
|
-
self.WORKGRACE =
|
|
109
|
-
self.
|
|
115
|
+
self.WORKGRACE = 3600 # 1 hour before a running job is judged on progress
|
|
116
|
+
self.GATHGRACE = 2*3600 # 2 hours before a job with nothing done yet counts as stalled
|
|
117
|
+
self.MINWPROJ = 1.5 # projected done fraction at the walltime that counts as finishing
|
|
110
118
|
self.PGBACK = {
|
|
111
119
|
'workdir' : "{}/{}/quasar_backup".format(self.PGLOG['GDEXWORK'], self.PGLOG['COMMONUSER']),
|
|
112
120
|
'mproc' : 1,
|
|
@@ -200,6 +208,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
200
208
|
# an even worker slot walks the datasets from the other end so that it and the odd
|
|
201
209
|
# slot before it meet in the middle instead of fighting over the same dataset locks
|
|
202
210
|
if self.reverse_dsids(): self.dsids.reverse()
|
|
211
|
+
self.order_dsids()
|
|
203
212
|
self.PGBACK['cmd'] = "dsquasar {}".format(' '.join(argv))
|
|
204
213
|
|
|
205
214
|
# do quasar backup process
|
|
@@ -273,6 +282,18 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
273
282
|
dsid = self.format_dataset_id(dsid)
|
|
274
283
|
if dsid not in self.dsids: self.dsids.append(dsid)
|
|
275
284
|
|
|
285
|
+
# put the -t datasets on the object store first for an odd worker slot and the ones on
|
|
286
|
+
# Glade first for an even one, keeping the given (or worker-reversed) order within each
|
|
287
|
+
# class; see dsid_order for why
|
|
288
|
+
def order_dsids(self):
|
|
289
|
+
if len(self.dsids) < 2: return
|
|
290
|
+
dcnd = "locflag = 'O' AND dsid IN ('{}')".format("', '".join(self.dsids))
|
|
291
|
+
pgrecs = self.pgmget('dataset', 'dsid', dcnd, self.LGWNEX)
|
|
292
|
+
if not pgrecs: return
|
|
293
|
+
odsids = set(pgrecs['dsid'])
|
|
294
|
+
reverse = self.reverse_dsids()
|
|
295
|
+
self.dsids.sort(key = lambda dsid: (dsid in odsids) == reverse)
|
|
296
|
+
|
|
276
297
|
# Check backup and file information and dump the backup statistics#
|
|
277
298
|
def dump_statistics_action(self):
|
|
278
299
|
self.start_dsquasar_none_daemon(self.STTACT)
|
|
@@ -373,7 +394,8 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
373
394
|
# file (one child per tar): new infiles CINACT will create this run (estimated from
|
|
374
395
|
# the total bytes of GDEX files ready to back up, ~one tar per TARSIZE) plus existing
|
|
375
396
|
# status 'N' infile records to build (TARACT), scaled one process per MPTLIMIT tar
|
|
376
|
-
# files
|
|
397
|
+
# files on Glade or one per MPOLIMIT tar files on the object store, the two summed and
|
|
398
|
+
# capped at MPTMAX. uploading's unit is the BCKSIZE transfer batch (one child
|
|
377
399
|
# per ~BCKSIZE/TARSIZE tar files): existing status 'T' tar records to transfer
|
|
378
400
|
# (BCKACT), scaled one process per MPBLIMIT tar files, capped at MPBMAX. the phases
|
|
379
401
|
# run sequentially in one run and share a single reserved ncpus, so return the max of
|
|
@@ -381,16 +403,34 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
381
403
|
# never reaches here and stays single process.
|
|
382
404
|
def batch_process_count(self, acts):
|
|
383
405
|
mproc = 1
|
|
384
|
-
|
|
406
|
+
gcnt = ocnt = 0 # tar files on Glade and on the object store
|
|
407
|
+
# an extra worker cannot touch the datasets another live process holds, typically the
|
|
408
|
+
# big object store dataset worker 1 is on, so it leaves them out of its sizing as its
|
|
409
|
+
# real gather will; counting them reserved worker 1's cpus again for the Glade work
|
|
410
|
+
# left. the gather's unlock pass skips a live holder and frees a dead one's lock.
|
|
411
|
+
# worker 1 keeps counting everything: a held dataset is usually released well within
|
|
412
|
+
# its 24 hours, and checking each holder costs an rdaps call per dataset
|
|
413
|
+
extra = self.PGBACK['worker'] > 1
|
|
385
414
|
if acts&self.CINACT:
|
|
386
|
-
sizes = [0]
|
|
387
|
-
if self.gather_dataset_files(None,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
415
|
+
sizes = [0, 0] # bytes on Glade and on the object store
|
|
416
|
+
if self.gather_dataset_files(None, extra, sizes):
|
|
417
|
+
gcnt += (sizes[0] + self.TARSIZE - 1)//self.TARSIZE
|
|
418
|
+
ocnt += (sizes[1] + self.TARSIZE - 1)//self.TARSIZE
|
|
419
|
+
if not (gcnt or ocnt): gcnt = 1
|
|
420
|
+
if acts&self.TARACT:
|
|
421
|
+
# a dataset lock left by a dead process also drops out here, though the gather
|
|
422
|
+
# would free it; that undercounts by a stale lock's infiles at most
|
|
423
|
+
xcnd = " AND dsid NOT IN (SELECT dsid FROM dataset WHERE pid > 0)" if extra else ''
|
|
424
|
+
ncnt = self.batch_tar_count('N', None, xcnd)
|
|
425
|
+
oncnt = self.batch_tar_count('N', 'O', xcnd) if ncnt else 0
|
|
426
|
+
gcnt += ncnt - oncnt
|
|
427
|
+
ocnt += oncnt
|
|
428
|
+
# ceil(gcnt/MPTLIMIT + ocnt/MPOLIMIT): one process per MPTLIMIT Glade tar files plus
|
|
429
|
+
# one per MPOLIMIT object store tar files, rounded up once over the sum
|
|
430
|
+
tunit = self.MPTLIMIT*self.MPOLIMIT
|
|
431
|
+
tproc = (gcnt*self.MPOLIMIT + ocnt*self.MPTLIMIT + tunit - 1)//tunit
|
|
432
|
+
if tproc > self.MPTMAX: tproc = self.MPTMAX
|
|
433
|
+
if tproc > mproc: mproc = tproc
|
|
394
434
|
if acts&self.BCKACT:
|
|
395
435
|
bcnt = self.batch_tar_count('T')
|
|
396
436
|
if bcnt > self.MPBLIMIT:
|
|
@@ -399,10 +439,25 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
399
439
|
if bproc > mproc: mproc = bproc
|
|
400
440
|
return mproc
|
|
401
441
|
|
|
402
|
-
#
|
|
403
|
-
|
|
442
|
+
# the -L scope of a bfile condition, or the scope of the given locflag. bfile has no
|
|
443
|
+
# locflag, so a record is matched through its dataset with the same "O" / "not O" split
|
|
444
|
+
# gather_dataset_files uses, keeping the two halves exhaustive. without it an -L O and
|
|
445
|
+
# an -L G run that overlap each tar, transfer and report the tar records of both halves
|
|
446
|
+
def bfile_locflag_condition(self, locflag = None):
|
|
447
|
+
if not locflag: locflag = self.PGBACK['locflag']
|
|
448
|
+
if not locflag: return ''
|
|
449
|
+
lnot = '' if locflag == 'O' else 'NOT '
|
|
450
|
+
return " AND dsid {}IN (SELECT dsid FROM dataset WHERE locflag = 'O')".format(lnot)
|
|
451
|
+
|
|
452
|
+
# count the bfile tar records of a given status honoring backflag/dataset/locflag scope;
|
|
453
|
+
# a locflag given narrows the count further to that half, inside any -L scope, and xcnd
|
|
454
|
+
# is any extra condition to AND on
|
|
455
|
+
def batch_tar_count(self, status, locflag = None, xcnd = ''):
|
|
404
456
|
bcnd = "status = '{}'".format(status)
|
|
405
457
|
if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
|
|
458
|
+
bcnd += self.bfile_locflag_condition()
|
|
459
|
+
if locflag: bcnd += self.bfile_locflag_condition(locflag)
|
|
460
|
+
bcnd += xcnd
|
|
406
461
|
if self.dsids:
|
|
407
462
|
tcnt = 0
|
|
408
463
|
for dsid in self.dsids:
|
|
@@ -431,11 +486,16 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
431
486
|
dargv = dargv[0:100]
|
|
432
487
|
return self.get_dscheck("dsquasar", dargv, self.PGBACK['workdir'], self.PGLOG['CURUID'], dargx, self.LOGWRN)
|
|
433
488
|
|
|
434
|
-
# a running batch job counts as stalled once it has run longer than WORKGRACE
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
# such a job cannot finish this attempt however long it is left alone, so an
|
|
438
|
-
# worker is what helps it, not more retries.
|
|
489
|
+
# a running batch job counts as stalled once it has run longer than WORKGRACE and its
|
|
490
|
+
# progress so far projects to less than MINWPROJ of its recorded work by the walltime,
|
|
491
|
+
# or once it has run longer than GATHGRACE with nothing recorded done at all:
|
|
492
|
+
# such a job cannot finish this attempt comfortably however long it is left alone, so an
|
|
493
|
+
# extra worker is what helps it, not more retries.
|
|
494
|
+
# MINWPROJ is above 1 on purpose: the first worker starts on the slow object store
|
|
495
|
+
# datasets and an extra one on the fast Glade ones (see dsid_order), so a second worker
|
|
496
|
+
# is wanted whenever the first is not well on its way to finishing, not only when it is
|
|
497
|
+
# sure to miss the walltime. 1.5 leaves alone only a job on pace to finish within about
|
|
498
|
+
# two thirds of the walltime.
|
|
439
499
|
# the test is on the RATE, not on an absolute done fraction as it first was. a fixed
|
|
440
500
|
# 1% threshold only catches a job that is nearly frozen, and reads a job that needs
|
|
441
501
|
# weeks as progressing: the real case was 2% done after 13H35M of a 24 hour walltime,
|
|
@@ -448,15 +508,19 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
448
508
|
# runs this and set_walltime_deadline reads the granted walltime back in the batch job.
|
|
449
509
|
# when the daemon caps it to a shorter queue limit the projection is optimistic, which
|
|
450
510
|
# errs towards leaving the job alone.
|
|
451
|
-
# a job that has recorded nothing done
|
|
452
|
-
# zero rate
|
|
453
|
-
#
|
|
454
|
-
#
|
|
511
|
+
# a job that has recorded nothing done may still be in its gather phases, which read as
|
|
512
|
+
# a zero rate, so it gets the longer GATHGRACE before it is judged. but a job that has
|
|
513
|
+
# recorded nothing after that long, typically one downloading its first object store
|
|
514
|
+
# dataset (dcount is only flushed every pstep files), is the slowest case of all and
|
|
515
|
+
# must not be left alone forever. the graces stay short because an extra worker can
|
|
516
|
+
# only be added by a cron submit, every 4 hours: a 6 hour grace kept it out of the first
|
|
517
|
+
# 6 to 10 hours of every attempt.
|
|
455
518
|
def worker_progress_stalled(self, pgrec):
|
|
456
519
|
if not (pgrec['pid'] and pgrec['stttime']): return False
|
|
457
520
|
if self.check_host_pid(pgrec['lockhost'], pgrec['pid']) <= 0: return False
|
|
458
521
|
elapsed = tm() - pgrec['stttime']
|
|
459
|
-
if elapsed < self.WORKGRACE
|
|
522
|
+
if elapsed < self.WORKGRACE: return False
|
|
523
|
+
if not pgrec['dcount']: return elapsed >= self.GATHGRACE
|
|
460
524
|
done = pgrec['dcount']/pgrec['fcount'] if pgrec['fcount'] else 0
|
|
461
525
|
return (done*self.WALLTIME/elapsed) < self.MINWPROJ
|
|
462
526
|
|
|
@@ -607,7 +671,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
607
671
|
self.check_batch_deadline()
|
|
608
672
|
if len(qinfo['dsids']) == self.DSCNT: self.process_one_backup_file(qinfo, True, False)
|
|
609
673
|
if self.PGBACK['dolock'] and dsid not in qinfo['dslocks']:
|
|
610
|
-
if self.
|
|
674
|
+
if self.lock_work_dataset(dsid) < 1: continue
|
|
611
675
|
qinfo['dslocks'].append(dsid)
|
|
612
676
|
if not self.regather_dataset_files(bfiles, dsid, backflag):
|
|
613
677
|
self.lock_dataset(dsid, 0, self.LGEREX)
|
|
@@ -673,12 +737,27 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
673
737
|
# run, including the primary one, are left alone. returns the datasets locked here, to be
|
|
674
738
|
# unlocked once the tar file is dispatched, or None if one of them is held by another
|
|
675
739
|
# worker, in which case nothing new is left locked and the tar file must wait for a later run
|
|
740
|
+
# lock a dataset before working on it. a dataset held by another live process is the
|
|
741
|
+
# normal case once a second worker or the other backup action runs, and the library
|
|
742
|
+
# wording for it ("Locked by N/PBS: Cannot Relock") was logged as an ERROR, which counted
|
|
743
|
+
# in the error count and the error email although the dataset is just skipped. lock
|
|
744
|
+
# quietly and report the skip as information instead, naming the holder; a database
|
|
745
|
+
# error is still logged as an error by lock_dataset. returns what lock_dataset returns:
|
|
746
|
+
# 1 locked, -1 held by another process, 0 no dataset record or a database error
|
|
747
|
+
def lock_work_dataset(self, dsid, logact = None):
|
|
748
|
+
lstat = self.lock_dataset(dsid, 1, 0)
|
|
749
|
+
if lstat < 0:
|
|
750
|
+
pgrec = self.pgget('dataset', 'pid, lockhost', "dsid = '{}'".format(dsid), self.LOGERR)
|
|
751
|
+
holder = "{}/{}".format(pgrec['pid'], pgrec['lockhost']) if pgrec else 'another process'
|
|
752
|
+
self.pglog("{}: skipped, locked by {}".format(dsid, holder), self.LOGACT if logact is None else logact)
|
|
753
|
+
return lstat
|
|
754
|
+
|
|
676
755
|
def lock_backup_datasets(self, qinfo, dsids):
|
|
677
756
|
dslocks = []
|
|
678
757
|
if not self.PGBACK['dolock']: return dslocks
|
|
679
758
|
for dsid in dsids:
|
|
680
759
|
if dsid in qinfo['dslocks']: continue
|
|
681
|
-
if self.
|
|
760
|
+
if self.lock_work_dataset(dsid, self.DTLACT) < 1:
|
|
682
761
|
self.unlock_backup_datasets(qinfo, dslocks)
|
|
683
762
|
return None
|
|
684
763
|
qinfo['dslocks'].append(dsid)
|
|
@@ -718,7 +797,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
718
797
|
'qsize' : 0, 'qcnt' : 0}
|
|
719
798
|
for dsid in bfiles:
|
|
720
799
|
if self.PGBACK['dolock']:
|
|
721
|
-
if self.
|
|
800
|
+
if self.lock_work_dataset(dsid) < 1: continue
|
|
722
801
|
qinfo['dslocks'].append(dsid)
|
|
723
802
|
for bid in bfiles[dsid]:
|
|
724
803
|
self.check_batch_deadline()
|
|
@@ -1225,6 +1304,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1225
1304
|
fd = None
|
|
1226
1305
|
fcate = filetype.lower()
|
|
1227
1306
|
tname = fcate + 'file'
|
|
1307
|
+
self.MISSCNT = 0 # files missing from their storage location
|
|
1228
1308
|
for i in range(fcnt):
|
|
1229
1309
|
self.check_batch_deadline()
|
|
1230
1310
|
pgrec = recs[i]
|
|
@@ -1249,6 +1329,12 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1249
1329
|
fd = None
|
|
1250
1330
|
self.process_one_backup_file(qinfo, True, (fcnt-i) > 1)
|
|
1251
1331
|
if fd: fd.close()
|
|
1332
|
+
mcnt = self.MISSCNT
|
|
1333
|
+
if mcnt:
|
|
1334
|
+
# one line per dataset reaches the email; the first file names are in the log
|
|
1335
|
+
s = 's' if mcnt > 1 else ''
|
|
1336
|
+
ftype = 'Saved' if fcate == 's' else 'Web'
|
|
1337
|
+
self.pglog("{}: {} of {} {} file{} missing from storage, not backed up".format(dsid, mcnt, fcnt, ftype, s), self.LOGACT)
|
|
1252
1338
|
|
|
1253
1339
|
# initialize dataset backup dict structure
|
|
1254
1340
|
@staticmethod
|
|
@@ -1393,7 +1479,21 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1393
1479
|
rcnt += 1
|
|
1394
1480
|
return rcnt
|
|
1395
1481
|
|
|
1396
|
-
#
|
|
1482
|
+
# log a GDEX file whose record waits for backup but whose data is not where its
|
|
1483
|
+
# locflag says. the check functions return None for a missing file without logging
|
|
1484
|
+
# it, so such a file used to be skipped silently, run after run. only the first
|
|
1485
|
+
# MISSLOG files of a dataset are named, so a dataset missing thousands of files does
|
|
1486
|
+
# not flood the log every run. returns None, which tells the caller the file is
|
|
1487
|
+
# missing rather than failed
|
|
1488
|
+
def missing_file_stat(self, dsid, cate, fname, path):
|
|
1489
|
+
self.MISSCNT += 1
|
|
1490
|
+
if self.MISSCNT <= self.MISSLOG:
|
|
1491
|
+
self.pglog("{}-{}-{}: Missing {}".format(dsid, cate.upper(), fname, path), self.LOGWRN)
|
|
1492
|
+
return None
|
|
1493
|
+
|
|
1494
|
+
# evaluate file stat on disk/object storage and modify RDADB record if not match.
|
|
1495
|
+
# returns True to back the file up, False when it cannot be checked, and None
|
|
1496
|
+
# when it is missing from its storage location
|
|
1397
1497
|
def evaluate_file_stat(self, dsid, cate, pgrec):
|
|
1398
1498
|
tname = cate + 'file'
|
|
1399
1499
|
fname = pgrec[tname]
|
|
@@ -1402,6 +1502,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1402
1502
|
bucket = 'gdex-decsdata' if cate == 's' else self.PGLOG['OBJCTBKT']
|
|
1403
1503
|
ofile = self.join_paths(dsid, fname)
|
|
1404
1504
|
info = self.check_object_file(ofile, bucket, 1, self.ERRACT)
|
|
1505
|
+
if info is None: return self.missing_file_stat(dsid, cate, fname, "{}:{}".format(bucket, ofile))
|
|
1405
1506
|
if not info: return False
|
|
1406
1507
|
# an object still matching its record on size and time, with its checksum
|
|
1407
1508
|
# recorded already, has nothing to evaluate: downloading it only to md5 it
|
|
@@ -1422,6 +1523,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1422
1523
|
if cate == 's': lfile += pgrec['type'] + '/'
|
|
1423
1524
|
lfile += fname
|
|
1424
1525
|
info = self.check_local_file(lfile, 33, self.ERRACT)
|
|
1526
|
+
if info is None: return self.missing_file_stat(dsid, cate, fname, lfile)
|
|
1425
1527
|
if not info: return False
|
|
1426
1528
|
lname = 'DSK'
|
|
1427
1529
|
record = {}
|
|
@@ -1472,11 +1574,20 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1472
1574
|
if self.pgget_wfile(dsid, 'wid', "bid " + bcnd, self.LGWNEX): fopt |= self.WOPT
|
|
1473
1575
|
return fopt
|
|
1474
1576
|
|
|
1475
|
-
# dataset ordering for gathering work
|
|
1476
|
-
#
|
|
1477
|
-
#
|
|
1478
|
-
|
|
1479
|
-
|
|
1577
|
+
# dataset ordering for gathering work. an odd worker slot starts on the datasets on the
|
|
1578
|
+
# object store and an even one on the datasets on Glade: an Object(O) dataset has every
|
|
1579
|
+
# file staged down from Boreas and checksummed before it can be tarred, while a Glade
|
|
1580
|
+
# one is only stat'ed, so the slow downloads get the most time from the first worker
|
|
1581
|
+
# while an added second worker clears the fast Glade work. an even slot walks the exact
|
|
1582
|
+
# reverse of an odd one (Glade from d999999 down, then the object store from d999999
|
|
1583
|
+
# down), so that a pair of workers meets in the middle instead of fighting over the same
|
|
1584
|
+
# dataset locks. dataset.locflag is matched as "O" / "not O" (a NULL locflag counts as
|
|
1585
|
+
# Glade), the same split -L uses. bfile has no locflag, so its records are matched
|
|
1586
|
+
# through their dataset
|
|
1587
|
+
def dsid_order(self, bfile = False):
|
|
1588
|
+
ocnd = "dsid IN (SELECT dsid FROM dataset WHERE locflag = 'O')" if bfile else "locflag = 'O'"
|
|
1589
|
+
dord = " DESC" if self.reverse_dsids() else ""
|
|
1590
|
+
return " ORDER BY CASE WHEN {} THEN 0 ELSE 1 END{}, dsid{}".format(ocnd, dord, dord)
|
|
1480
1591
|
|
|
1481
1592
|
# whether this worker slot walks the datasets from the high end
|
|
1482
1593
|
def reverse_dsids(self):
|
|
@@ -1487,11 +1598,9 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1487
1598
|
fcnt = 0
|
|
1488
1599
|
# warn per named dataset with no files; stay silent when scanning all datasets
|
|
1489
1600
|
logact = self.LOGWRN if self.dsids else 0
|
|
1490
|
-
flds = "dsid, backflag, pid, lockhost"
|
|
1491
|
-
# -L
|
|
1492
|
-
#
|
|
1493
|
-
# one is only stat'ed, so the two classes run at wildly different rates and are better
|
|
1494
|
-
# off in separate runs than sharing one walltime.
|
|
1601
|
+
flds = "dsid, backflag, locflag, pid, lockhost"
|
|
1602
|
+
# -L limits the run to where the data sits. it is optional: without it an odd worker
|
|
1603
|
+
# slot starts on the object store datasets and an even one on Glade (see dsid_order).
|
|
1495
1604
|
# G is matched as "not O" rather than "= G" on purpose: dataset.locflag is only a
|
|
1496
1605
|
# summary of the per-file flags (set_dataset_locflag writes it only when every public
|
|
1497
1606
|
# web file agrees, and leaves the old value otherwise), so it can hold a stale value.
|
|
@@ -1532,7 +1641,14 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1532
1641
|
if lstat < 0:
|
|
1533
1642
|
self.pglog("{}: skipped, still in use by {}/{}".format(dsid, pgrec['pid'], pgrec['lockhost']), self.LOGACT)
|
|
1534
1643
|
continue
|
|
1535
|
-
|
|
1644
|
+
if sizes is None:
|
|
1645
|
+
fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact)
|
|
1646
|
+
else:
|
|
1647
|
+
# sizes holds the bytes ready on Glade and on the object store apart, since a
|
|
1648
|
+
# tar file of each takes a very different time to build (see MPOLIMIT)
|
|
1649
|
+
dsizes = [0]
|
|
1650
|
+
fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact, dsizes)
|
|
1651
|
+
sizes[1 if pgrec['locflag'] == 'O' else 0] += dsizes[0]
|
|
1536
1652
|
if dsfiles:
|
|
1537
1653
|
s = 's' if fcnt > 1 else ''
|
|
1538
1654
|
bmsg = self.BACKMSG[self.PGBACK['backflag']] if self.PGBACK['backflag'] else 'backup'
|
|
@@ -1546,10 +1662,11 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1546
1662
|
bcnd = "status = 'N'"
|
|
1547
1663
|
flds = "bid, dsid, bfile, type, data_size size, (scount + wcount) fcnt, dsids, note"
|
|
1548
1664
|
if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
|
|
1665
|
+
bcnd += self.bfile_locflag_condition()
|
|
1549
1666
|
if self.dsids:
|
|
1550
1667
|
cnds = ["dsid = '{}' AND {}".format(dsid, bcnd) for dsid in self.dsids]
|
|
1551
1668
|
else:
|
|
1552
|
-
cnds = [bcnd + self.dsid_order()]
|
|
1669
|
+
cnds = [bcnd + self.dsid_order(True)]
|
|
1553
1670
|
for dcnd in cnds:
|
|
1554
1671
|
pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
|
|
1555
1672
|
bcnt = len(pgrecs['bid']) if pgrecs else 0
|
|
@@ -1572,10 +1689,11 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1572
1689
|
bcnd = "status = 'T'"
|
|
1573
1690
|
flds = "bid, dsid, bfile, type, data_size size, (scount + wcount) fcnt, note"
|
|
1574
1691
|
if self.PGBACK['backflag']: bcnd += " AND type = '{}'".format(self.PGBACK['backflag'])
|
|
1692
|
+
bcnd += self.bfile_locflag_condition()
|
|
1575
1693
|
if self.dsids:
|
|
1576
1694
|
cnds = ["dsid = '{}' AND {}".format(dsid, bcnd) for dsid in self.dsids]
|
|
1577
1695
|
else:
|
|
1578
|
-
cnds = [bcnd + self.dsid_order()]
|
|
1696
|
+
cnds = [bcnd + self.dsid_order(True)]
|
|
1579
1697
|
for dcnd in cnds:
|
|
1580
1698
|
pgrecs = self.pgmget("bfile", flds, dcnd, self.LGWNEX)
|
|
1581
1699
|
bcnt = len(pgrecs['bid']) if pgrecs else 0
|
{rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/dsquasar.usg
RENAMED
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
store, G takes everything else. Applies to -a and to -t alike. The
|
|
30
30
|
two classes back up at very different rates: an O dataset has every
|
|
31
31
|
file staged down from the object store and checksummed before it can
|
|
32
|
-
be tarred, while a G dataset is only stat'ed.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
all datasets.
|
|
32
|
+
be tarred, while a G dataset is only stat'ed. -L is optional: without
|
|
33
|
+
it the first worker (and a run without -w) starts on the O datasets,
|
|
34
|
+
so the slow downloads get the most time, and a second worker (-w 2)
|
|
35
|
+
starts on the G datasets. Defaults to all datasets.
|
|
36
36
|
|
|
37
37
|
-L G and -L O together always cover every dataset, so a pair of runs
|
|
38
38
|
loses nothing. The split is by dataset, though, and dataset.locflag
|
|
@@ -97,9 +97,11 @@
|
|
|
97
97
|
-m ProcessCount
|
|
98
98
|
Number of concurrent child processes. Defaults to 1. When -m is not
|
|
99
99
|
given, a delayed backup run sizes the count from the number of tar
|
|
100
|
-
files it expects to handle
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
files it expects to handle (one process per 200 tar files on Glade
|
|
101
|
+
and per 20 on the object store, whose files are downloaded first, up
|
|
102
|
+
to 12) and reserves matching PBS cpus, so in delay mode the process
|
|
103
|
+
count normally travels through the reserved cpus rather than the
|
|
104
|
+
command line. An explicit -m overrides that sizing,
|
|
103
105
|
and -m 1 forces a single process.
|
|
104
106
|
|
|
105
107
|
-n Gather and show the available file counts only; no backup actions are
|
|
@@ -108,11 +110,12 @@
|
|
|
108
110
|
-W MaxWorkers
|
|
109
111
|
Maximum number of concurrent batch workers for the same command.
|
|
110
112
|
Defaults to 2. A repeat delayed submit normally blocks while the
|
|
111
|
-
batch job is running; if that job has run for more than
|
|
112
|
-
|
|
113
|
-
less than
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
batch job is running; if that job has run for more than 1 hour and
|
|
114
|
+
at its rate so far would not finish its recorded work within about
|
|
115
|
+
two thirds of the walltime (less than 150% of it by the walltime),
|
|
116
|
+
or has run for more than 2 hours with nothing recorded done yet, it
|
|
117
|
+
is treated as stalled and an extra worker is submitted instead, up
|
|
118
|
+
to this limit. Extra workers rely on dataset locking to stay off each
|
|
116
119
|
other's files, so this is pinned to 1 with -l N, and also for the
|
|
117
120
|
transfer action (-A 4, 6, 7), which claims tar files without locking.
|
|
118
121
|
|
|
@@ -79,6 +79,8 @@ class DsTACC(PgCMD, PgSplit):
|
|
|
79
79
|
self.TARSIZE = 1024*self.PGLOG['ONEGBS'] # 1TB, tarfile size limit
|
|
80
80
|
self.MINSIZE = 512*self.PGLOG['ONEGBS'] # .5TB, minimal tarfile size
|
|
81
81
|
self.ONESIZE = 20*self.PGLOG['ONEGBS'] # 20GB, minimal file size to tar a single file
|
|
82
|
+
self.MISSLOG = 10 # files named in the log per dataset when missing from storage
|
|
83
|
+
self.MISSCNT = 0 # files of the current dataset missing from storage
|
|
82
84
|
self.TFCOUNT = 1000000 # if file count is greater, use self.MINSIZE for tar file
|
|
83
85
|
self.PGBACK = {
|
|
84
86
|
'workdir' : "{}/{}/tacc_backup".format(self.PGLOG['GDEXWORK'], self.PGLOG['COMMONUSER']),
|
|
@@ -731,6 +733,7 @@ class DsTACC(PgCMD, PgSplit):
|
|
|
731
733
|
fd = None
|
|
732
734
|
fcate = filetype.lower()
|
|
733
735
|
tname = fcate + 'file'
|
|
736
|
+
self.MISSCNT = 0 # files missing from their storage location
|
|
734
737
|
for i in range(fcnt):
|
|
735
738
|
pgrec = recs[i]
|
|
736
739
|
if not self.evaluate_file_stat(dsid, fcate, pgrec): continue
|
|
@@ -754,6 +757,12 @@ class DsTACC(PgCMD, PgSplit):
|
|
|
754
757
|
fd = None
|
|
755
758
|
self.process_one_backup_file(qinfo, True, (fcnt-i) > 1)
|
|
756
759
|
if fd: fd.close()
|
|
760
|
+
mcnt = self.MISSCNT
|
|
761
|
+
if mcnt:
|
|
762
|
+
# one line per dataset reaches the email; the first file names are in the log
|
|
763
|
+
s = 's' if mcnt > 1 else ''
|
|
764
|
+
ftype = 'Saved' if fcate == 's' else 'Web'
|
|
765
|
+
self.pglog("{}: {} of {} {} file{} missing from storage, not backed up".format(dsid, mcnt, fcnt, ftype, s), self.LOGACT)
|
|
757
766
|
|
|
758
767
|
# initialize dataset backup dict structure
|
|
759
768
|
@staticmethod
|
|
@@ -851,7 +860,21 @@ class DsTACC(PgCMD, PgSplit):
|
|
|
851
860
|
rcnt += 1
|
|
852
861
|
return rcnt
|
|
853
862
|
|
|
854
|
-
#
|
|
863
|
+
# log a GDEX file whose record waits for backup but whose data is not where its
|
|
864
|
+
# locflag says. the check functions return None for a missing file without logging
|
|
865
|
+
# it, so such a file used to be skipped silently, run after run. only the first
|
|
866
|
+
# MISSLOG files of a dataset are named, so a dataset missing thousands of files does
|
|
867
|
+
# not flood the log every run. returns None, which tells the caller the file is
|
|
868
|
+
# missing rather than failed
|
|
869
|
+
def missing_file_stat(self, dsid, cate, fname, path):
|
|
870
|
+
self.MISSCNT += 1
|
|
871
|
+
if self.MISSCNT <= self.MISSLOG:
|
|
872
|
+
self.pglog("{}-{}-{}: Missing {}".format(dsid, cate.upper(), fname, path), self.LOGWRN)
|
|
873
|
+
return None
|
|
874
|
+
|
|
875
|
+
# evaluate file stat on disk/object storage and modify RDADB record if not match.
|
|
876
|
+
# returns True to back the file up, False when it cannot be checked, and None
|
|
877
|
+
# when it is missing from its storage location
|
|
855
878
|
def evaluate_file_stat(self, dsid, cate, pgrec):
|
|
856
879
|
tname = cate + 'file'
|
|
857
880
|
fname = pgrec[tname]
|
|
@@ -860,6 +883,7 @@ class DsTACC(PgCMD, PgSplit):
|
|
|
860
883
|
bucket = 'gdex-decsdata' if cate == 's' else self.PGLOG['OBJCTBKT']
|
|
861
884
|
ofile = self.join_paths(dsid, fname)
|
|
862
885
|
info = self.check_object_file(ofile, bucket, 1, self.ERRACT)
|
|
886
|
+
if info is None: return self.missing_file_stat(dsid, cate, fname, "{}:{}".format(bucket, ofile))
|
|
863
887
|
if not info: return False
|
|
864
888
|
# an object still matching its record on size and time, with its checksum
|
|
865
889
|
# recorded already, has nothing to evaluate: downloading it only to md5 it
|
|
@@ -880,6 +904,7 @@ class DsTACC(PgCMD, PgSplit):
|
|
|
880
904
|
if cate == 's': lfile += pgrec['type'] + '/'
|
|
881
905
|
lfile += fname
|
|
882
906
|
info = self.check_local_file(lfile, 33, self.ERRACT)
|
|
907
|
+
if info is None: return self.missing_file_stat(dsid, cate, fname, lfile)
|
|
883
908
|
if not info: return False
|
|
884
909
|
lname = 'DSK'
|
|
885
910
|
record = {}
|
{rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25/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.25
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/__init__.py
RENAMED
|
File without changes
|
{rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/ds_quasar.py
RENAMED
|
File without changes
|
{rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/taccrec.py
RENAMED
|
File without changes
|
{rda_python_dsquasar-3.0.23 → rda_python_dsquasar-3.0.25}/src/rda_python_dsquasar/tacctar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|