rda-python-dsquasar 3.0.20__tar.gz → 3.0.22__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.20/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.22}/PKG-INFO +1 -1
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/pyproject.toml +1 -1
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/dsquasar.py +60 -43
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/dsquasar.usg +17 -2
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22/src/rda_python_dsquasar.egg-info}/PKG-INFO +1 -1
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/LICENSE +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/MANIFEST.in +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/README.md +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/setup.cfg +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/__init__.py +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/ds_quasar.py +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/dstacc.py +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/taccrec.py +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/tacctar.py +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar.egg-info/SOURCES.txt +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar.egg-info/dependency_links.txt +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar.egg-info/entry_points.txt +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar.egg-info/requires.txt +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar.egg-info/top_level.txt +0 -0
- {rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/test/test_dsquasar.py +0 -0
{rda_python_dsquasar-3.0.20/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.22}/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.22
|
|
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.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/dsquasar.py
RENAMED
|
@@ -23,7 +23,6 @@ import os
|
|
|
23
23
|
import re
|
|
24
24
|
import sys
|
|
25
25
|
import time
|
|
26
|
-
import signal
|
|
27
26
|
from os import path as op
|
|
28
27
|
from time import time as tm
|
|
29
28
|
from rda_python_common.pg_cmd import PgCMD
|
|
@@ -124,6 +123,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
124
123
|
'maxcnt' : 10,
|
|
125
124
|
'dolock' : 1,
|
|
126
125
|
'doemail' : 0,
|
|
126
|
+
'locflag' : None, # -L, limit the datasets gathered to one dataset.locflag
|
|
127
127
|
'starttime' : 0, # wall-clock start of the run, for the PBS walltime guard
|
|
128
128
|
'einfo' : 0, # set once a progress report is cached into dscheck.einfo
|
|
129
129
|
'eretry' : 0, # earliest retry time after a progress report failed to cache
|
|
@@ -145,7 +145,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
145
145
|
argv = sys.argv[1:]
|
|
146
146
|
for arg in argv:
|
|
147
147
|
if re.match(r'^-(h|-help)$', arg, re.I): self.show_usage('dsquasar')
|
|
148
|
-
ms = re.match(r'^-(a|b|c|d|e|E|l|m|n|t|u|w|A|B|D|W)$', arg)
|
|
148
|
+
ms = re.match(r'^-(a|b|c|d|e|E|l|m|n|t|u|w|A|B|D|L|W)$', arg)
|
|
149
149
|
if ms:
|
|
150
150
|
arg = ms.group(1)
|
|
151
151
|
if arg == 'b':
|
|
@@ -153,7 +153,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
153
153
|
elif arg in self.sopts:
|
|
154
154
|
self.sopts[arg] = 1
|
|
155
155
|
option = None
|
|
156
|
-
elif '
|
|
156
|
+
elif 'AcdLlmtwW'.find(arg) > -1:
|
|
157
157
|
option = arg
|
|
158
158
|
if arg == 'd': self.bopts = []
|
|
159
159
|
elif 'BD'.find(arg) > -1:
|
|
@@ -180,6 +180,10 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
180
180
|
if not (arg == 'Y' or arg == 'N'): self.pglog(arg +": Lock Flag(-l) must be Y or N", self.LGWNEX)
|
|
181
181
|
self.PGBACK['dolock'] = 1 if arg == 'Y' else 0
|
|
182
182
|
option = None
|
|
183
|
+
elif option == 'L':
|
|
184
|
+
if not (arg == 'G' or arg == 'O'): self.pglog(arg +": Location Flag(-L) must be G or O", self.LGWNEX)
|
|
185
|
+
self.PGBACK['locflag'] = arg
|
|
186
|
+
option = None
|
|
183
187
|
elif option == 'W':
|
|
184
188
|
self.PGBACK['maxworkers'] = int(arg)
|
|
185
189
|
if self.PGBACK['maxworkers'] < 1: self.pglog(arg +": Maximum Workers(-W) must be > 0", self.LGWNEX)
|
|
@@ -203,7 +207,7 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
203
207
|
self.cmdlog(self.PGBACK['cmd'])
|
|
204
208
|
self.PGBACK['starttime'] = tm()
|
|
205
209
|
self.set_walltime_deadline()
|
|
206
|
-
self.
|
|
210
|
+
self.catch_term_signal(self.batch_term_report)
|
|
207
211
|
if self.sopts['u']:
|
|
208
212
|
if self.sopts['a']: self.pglog("-u: Dataset IDs must be provided to Unlock datasets", self.LOGWRN)
|
|
209
213
|
self.unlock_datasets()
|
|
@@ -852,42 +856,29 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
852
856
|
self.PGLOG['CURBID'], self.seconds_to_string_time(self.WALLTIME),
|
|
853
857
|
self.seconds_to_string_time(self.MAXRUNTIME)), self.LOGWRN)
|
|
854
858
|
|
|
855
|
-
#
|
|
856
|
-
#
|
|
857
|
-
#
|
|
858
|
-
# it
|
|
859
|
-
#
|
|
860
|
-
#
|
|
861
|
-
|
|
862
|
-
if self.PGLOG['CURBID'] < 1: return
|
|
863
|
-
signal.signal(signal.SIGTERM, self.batch_term_handler)
|
|
864
|
-
|
|
865
|
-
# park a progress report and then die of the signal that was sent. this runs on borrowed
|
|
866
|
-
# time, so it does only what it must: no tar queue counts (two database queries we may
|
|
867
|
-
# not get to finish - the done count comes from memory), no dataset unlocking (the
|
|
868
|
-
# dscheck daemon already cleans up after a dead pid). the default handler is restored
|
|
869
|
-
# first so that a second signal, or the SIGKILL that follows, ends the run outright
|
|
870
|
-
# instead of re-entering here should the report hang.
|
|
859
|
+
# park a progress report for a run PBS is about to kill. catch_term_signal() arms this
|
|
860
|
+
# for a batch run only, restores the default handler before calling it, and re-raises
|
|
861
|
+
# the signal afterwards, so all this has to do is build the report.
|
|
862
|
+
# it runs on borrowed time (SIGKILL follows SIGTERM by the MoM's kill_delay, 10 seconds
|
|
863
|
+
# by default), so it does only what it must: no tar queue counts (two database queries
|
|
864
|
+
# we may not get to finish - the done count comes from memory), no dataset unlocking
|
|
865
|
+
# (the dscheck daemon already cleans up after a dead pid).
|
|
871
866
|
# the report may have to be written from inside an interrupted database call; that is
|
|
872
867
|
# why it goes through report_dscheck_email, whose cache_customized_email falls back to
|
|
873
868
|
# sending the mail directly when the UPDATE fails.
|
|
874
|
-
def
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
title = "dsquasar: {} terminated ({})".format(amsg, dmsg)
|
|
888
|
-
if self.PGBACK['errcnt']: title += " Error({})".format(self.PGBACK['errcnt'])
|
|
889
|
-
self.report_dscheck_email(title, 1)
|
|
890
|
-
os.kill(os.getpid(), signum)
|
|
869
|
+
def batch_term_report(self, signum, frame):
|
|
870
|
+
if self.PGBACK['einfo'] or not (self.PGBACK['doemail'] and self.PGLOG['DSCHECK']): return
|
|
871
|
+
etime = self.seconds_to_string_time(int(tm() - self.PGBACK['starttime']))
|
|
872
|
+
amsg = self.action_message()
|
|
873
|
+
dmsg = self.batch_done_count()
|
|
874
|
+
wmsg = self.seconds_to_string_time(self.WALLTIME)
|
|
875
|
+
self.pglog(self.INDENT + "{}: Terminated by signal {} after {} of the {} PBS walltime, {}".format(
|
|
876
|
+
amsg, signum, etime, wmsg, dmsg), self.LOGACT)
|
|
877
|
+
self.set_email("{}: {} terminated after {} of the {} PBS walltime, {}!".format(
|
|
878
|
+
self.PGBACK['cmd'], amsg, etime, wmsg, dmsg), self.EMLTOP)
|
|
879
|
+
title = "dsquasar: {} terminated ({})".format(amsg, dmsg)
|
|
880
|
+
if self.PGBACK['errcnt']: title += " Error({})".format(self.PGBACK['errcnt'])
|
|
881
|
+
self.report_dscheck_email(title, 1)
|
|
891
882
|
|
|
892
883
|
# email a report for a run under dscheck control. the progress report is parked in
|
|
893
884
|
# dscheck.einfo and the final report is sent right away. the run holds the check lock
|
|
@@ -932,9 +923,10 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
932
923
|
# even if PBS kills the job at the walltime. the final report follows and drops the
|
|
933
924
|
# parked one if the run does finish in time. the live email buffers are saved and put
|
|
934
925
|
# back, so the final report still carries everything logged before the cutoff.
|
|
935
|
-
#
|
|
936
|
-
#
|
|
937
|
-
#
|
|
926
|
+
# batch_term_report covers the kill itself, but only the seconds of it: SIGKILL cannot be
|
|
927
|
+
# caught, and a process stuck in uninterruptible I/O may never run the handler. this parked
|
|
928
|
+
# report is what stands between a killed job and a run that is never heard from, so it is
|
|
929
|
+
# kept as the first line of defence. every action submitted to PBS is guarded, not just the
|
|
938
930
|
# tar and transfer ones: -A 16 and the hidden -A 128 are submitted with the same 24 hour
|
|
939
931
|
# walltime and used to be excluded, so they died silently.
|
|
940
932
|
# the tar queue depths are only reported for the actions they describe. both depths are
|
|
@@ -1482,13 +1474,29 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1482
1474
|
fcnt = 0
|
|
1483
1475
|
# warn per named dataset with no files; stay silent when scanning all datasets
|
|
1484
1476
|
logact = self.LOGWRN if self.dsids else 0
|
|
1477
|
+
flds = "dsid, backflag, pid, lockhost"
|
|
1478
|
+
# -L splits the work by where the data sits: an Object(O) dataset has to be staged
|
|
1479
|
+
# down from the object store and checksummed before it can be tarred, while a Glade(G)
|
|
1480
|
+
# one is only stat'ed, so the two classes run at wildly different rates and are better
|
|
1481
|
+
# off in separate runs than sharing one walltime.
|
|
1482
|
+
# G is matched as "not O" rather than "= G" on purpose: dataset.locflag is only a
|
|
1483
|
+
# summary of the per-file flags (set_dataset_locflag writes it only when every public
|
|
1484
|
+
# web file agrees, and leaves the old value otherwise), so it can hold a stale value.
|
|
1485
|
+
# as "not O" the two halves stay exhaustive and no dataset falls through both runs
|
|
1486
|
+
lcnd = ''
|
|
1487
|
+
if self.PGBACK['locflag'] == 'O':
|
|
1488
|
+
lcnd = "locflag = 'O'"
|
|
1489
|
+
elif self.PGBACK['locflag']:
|
|
1490
|
+
lcnd = "locflag <> 'O'"
|
|
1485
1491
|
if self.dsids:
|
|
1486
1492
|
pgrecs = []
|
|
1487
1493
|
for dsid in self.dsids:
|
|
1488
|
-
|
|
1494
|
+
dcnd = "dsid = '{}'".format(dsid)
|
|
1495
|
+
if lcnd: dcnd += " AND " + lcnd
|
|
1496
|
+
pgrec = self.pgget("dataset", flds, dcnd, self.LGWNEX)
|
|
1489
1497
|
if pgrec: pgrecs.append(pgrec)
|
|
1490
1498
|
else:
|
|
1491
|
-
mrecs = self.pgmget("dataset",
|
|
1499
|
+
mrecs = self.pgmget("dataset", flds, (lcnd + self.dsid_order()).strip(), self.LGWNEX)
|
|
1492
1500
|
dcnt = len(mrecs['dsid']) if mrecs else 0
|
|
1493
1501
|
pgrecs = [self.onerecord(mrecs, i) for i in range(dcnt)]
|
|
1494
1502
|
for pgrec in pgrecs:
|
|
@@ -1501,7 +1509,16 @@ class DsQuasar(PgCMD, PgSplit):
|
|
|
1501
1509
|
# main runs take new files (bid = 0), so it shares no record with the worker holding
|
|
1502
1510
|
# the lock and must not skip the dataset for it
|
|
1503
1511
|
if unlock and pgrec['pid'] and not self.PGBACK['chgdays']:
|
|
1504
|
-
|
|
1512
|
+
# quiet unlock: a live holder is the normal case for a second worker, and the
|
|
1513
|
+
# library wording for it ("Cannot Unlock") reads like a failure. report the skip
|
|
1514
|
+
# here instead, in terms of the worker that is still on the dataset
|
|
1515
|
+
lstat = self.lock_dataset(dsid, 0, 0)
|
|
1516
|
+
if lstat < 1:
|
|
1517
|
+
# -1 is the live holder; 0 means the record went away or the db errored,
|
|
1518
|
+
# which lock_dataset has already logged as an error
|
|
1519
|
+
if lstat < 0:
|
|
1520
|
+
self.pglog("{}: skipped, still in use by {}/{}".format(dsid, pgrec['pid'], pgrec['lockhost']), self.LOGACT)
|
|
1521
|
+
continue
|
|
1505
1522
|
fcnt += self.get_dataset_files(dsid, dsfiles, pgrec['backflag'], logact, sizes)
|
|
1506
1523
|
if dsfiles:
|
|
1507
1524
|
s = 's' if fcnt > 1 else ''
|
{rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/dsquasar.usg
RENAMED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
file tarring and transferring is performed through the utility program dsarch.
|
|
9
9
|
|
|
10
10
|
Usage: dsquasar [-b] [(-a | -t DatasetIDs)] [-c ChangeDays] [-(e|E)] \
|
|
11
|
-
[-(B|D)] [-A ActionBits] [-l (Y|N)]
|
|
12
|
-
[-n] [-u] [-d [HostName] [TryCount]] [-h]
|
|
11
|
+
[-(B|D)] [-L (G|O)] [-A ActionBits] [-l (Y|N)] \
|
|
12
|
+
[-m ProcessCount] [-n] [-u] [-d [HostName] [TryCount]] [-h]
|
|
13
13
|
|
|
14
14
|
Either -a or -t must be given (usage is displayed otherwise). dsquasar runs
|
|
15
15
|
from the Quasar backup work directory; it changes there automatically.
|
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
'%' is allowed and is expanded against the dataset table. Ignored,
|
|
25
25
|
with a warning, when -a is also given.
|
|
26
26
|
|
|
27
|
+
-L (G|O)
|
|
28
|
+
Limit the run by dataset.locflag: O takes the datasets on the object
|
|
29
|
+
store, G takes everything else. Applies to -a and to -t alike. The
|
|
30
|
+
two classes back up at very different rates: an O dataset has every
|
|
31
|
+
file staged down from the object store and checksummed before it can
|
|
32
|
+
be tarred, while a G dataset is only stat'ed. Running them in one job
|
|
33
|
+
lets the slow class eat the whole walltime, so give them separate
|
|
34
|
+
runs, e.g. one submit with -L G and another with -L O. Defaults to
|
|
35
|
+
all datasets.
|
|
36
|
+
|
|
37
|
+
-L G and -L O together always cover every dataset, so a pair of runs
|
|
38
|
+
loses nothing. The split is by dataset, though, and dataset.locflag
|
|
39
|
+
is only a summary of the per-file flags, so a dataset holding files of
|
|
40
|
+
both kinds is backed up entirely by whichever run claims it.
|
|
41
|
+
|
|
27
42
|
|
|
28
43
|
BACKUP SCOPE
|
|
29
44
|
|
{rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22/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.22
|
|
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.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/__init__.py
RENAMED
|
File without changes
|
{rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/ds_quasar.py
RENAMED
|
File without changes
|
|
File without changes
|
{rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/src/rda_python_dsquasar/taccrec.py
RENAMED
|
File without changes
|
{rda_python_dsquasar-3.0.20 → rda_python_dsquasar-3.0.22}/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
|