rda-python-dsarch 3.0.18__tar.gz → 3.0.19__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.
Files changed (21) hide show
  1. {rda_python_dsarch-3.0.18/src/rda_python_dsarch.egg-info → rda_python_dsarch-3.0.19}/PKG-INFO +1 -1
  2. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/pyproject.toml +1 -1
  3. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/dsarch.py +384 -67
  4. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/dsarch.usg +11 -9
  5. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/pg_arch.py +34 -10
  6. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19/src/rda_python_dsarch.egg-info}/PKG-INFO +1 -1
  7. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/LICENSE +0 -0
  8. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/MANIFEST.in +0 -0
  9. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/README.md +0 -0
  10. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/setup.cfg +0 -0
  11. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/PgArch.py +0 -0
  12. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/PgMeta.py +0 -0
  13. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/__init__.py +0 -0
  14. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/ds_arch.py +0 -0
  15. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch/pg_meta.py +0 -0
  16. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch.egg-info/SOURCES.txt +0 -0
  17. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch.egg-info/dependency_links.txt +0 -0
  18. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch.egg-info/entry_points.txt +0 -0
  19. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch.egg-info/requires.txt +0 -0
  20. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/src/rda_python_dsarch.egg-info/top_level.txt +0 -0
  21. {rda_python_dsarch-3.0.18 → rda_python_dsarch-3.0.19}/tests/test_dsarch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsarch
3
- Version: 3.0.18
3
+ Version: 3.0.19
4
4
  Summary: RDA Python package for utility program dsarch archive RDA data
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsarch
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_dsarch"
7
- version = "3.0.18"
7
+ version = "3.0.19"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -58,6 +58,8 @@ class DsArch(PgArch, PgMeta):
58
58
  self.BCKNOTES = {}
59
59
  self.VINDEX = {}
60
60
  self.CHGGRPS = {}
61
+ self.CPFILES = {} # file index -> copy/move child pname (or 1/0) of an -AL first pass
62
+ self.CPDIRS = {} # file index -> cross copy direction ('G','O','B','D') of an -AL first pass
61
63
  self.ERRMSG = ''
62
64
 
63
65
  def read_parameters(self):
@@ -115,16 +117,16 @@ class DsArch(PgArch, PgMeta):
115
117
  if self.PGOPT['ACTS'] == self.OPTS['AH'][0]:
116
118
  self.ALLCNT = len(self.params['HF']) if 'HF' in self.params else len(self.params['LF'])
117
119
  if 'XC' in self.params:
118
- self.crosscopy_help_files('Copy')
120
+ self.archive_in_passes(self.crosscopy_help_files, 'Copy')
119
121
  elif 'XM' in self.params:
120
- self.crosscopy_help_files('Move')
122
+ self.archive_in_passes(self.crosscopy_help_files, 'Move')
121
123
  else:
122
124
  self.archive_in_passes(self.archive_help_files)
123
125
  if 'CL' in self.params: self.delete_local_files()
124
126
  elif self.PGOPT['ACTS'] == self.OPTS['AQ'][0]:
125
127
  if 'XC' in self.params:
126
128
  self.ALLCNT = len(self.params['QF'])
127
- self.crosscopy_backup_files()
129
+ self.archive_in_passes(self.crosscopy_backup_files)
128
130
  else:
129
131
  self.archive_backup_file()
130
132
  elif self.PGOPT['ACTS'] == self.OPTS['AS'][0]:
@@ -137,9 +139,9 @@ class DsArch(PgArch, PgMeta):
137
139
  if 'ML' in self.params and 'GX' in self.params: del self.params['ML']
138
140
  self.cache_group_info(self.ALLCNT)
139
141
  if 'XC' in self.params:
140
- self.crosscopy_web_files('Copy')
142
+ self.archive_in_passes(self.crosscopy_web_files, 'Copy')
141
143
  elif 'XM' in self.params:
142
- self.crosscopy_web_files('Move')
144
+ self.archive_in_passes(self.crosscopy_web_files, 'Move')
143
145
  else:
144
146
  self.archive_in_passes(self.archive_web_files)
145
147
  if 'CL' in self.params: self.delete_local_files()
@@ -205,17 +207,17 @@ class DsArch(PgArch, PgMeta):
205
207
  (len(self.params['QF']) if 'QF' in self.params else 0))))
206
208
  if 'DD' in self.params: self.record_delete_directory(None, self.params['DD'])
207
209
  if 'HF' in self.params:
208
- self.move_help_files()
210
+ self.archive_in_passes(self.move_help_files)
209
211
  elif 'TS' in self.params:
210
- self.web_to_saved_files()
212
+ self.archive_in_passes(self.web_to_saved_files)
211
213
  elif 'TW' in self.params:
212
- self.saved_to_web_files()
214
+ self.archive_in_passes(self.saved_to_web_files)
213
215
  elif 'SF' in self.params:
214
- self.move_saved_files()
216
+ self.archive_in_passes(self.move_saved_files)
215
217
  elif 'WF' in self.params:
216
- self.move_web_files()
218
+ self.archive_in_passes(self.move_web_files)
217
219
  else:
218
- self.move_backup_files()
220
+ self.archive_in_passes(self.move_backup_files)
219
221
  if 'DD' in self.params: self.clean_delete_directory(self.PGOPT['wrnlog'])
220
222
  elif self.PGOPT['ACTS'] == self.OPTS['RQ'][0]:
221
223
  if 'WF' in self.params or 'SF' in self.params:
@@ -975,12 +977,14 @@ class DsArch(PgArch, PgMeta):
975
977
  self.reset_rdadb_version(dsid)
976
978
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
977
979
 
978
- def crosscopy_web_files(self, aname):
980
+ def crosscopy_web_files(self, aname, cpass=0):
979
981
  """
980
982
  Cross-copy (or cross-move) web files between GLADE and the object store.
981
983
 
982
984
  Args:
983
985
  aname -- 'Copy' to copy files, 'Move' to move (deletes the source)
986
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
987
+ copy pass and 2 for the record pass (see archive_in_passes())
984
988
  """
985
989
  tname = 'wfile'
986
990
  dsid = self.params['DS']
@@ -1004,7 +1008,7 @@ class DsArch(PgArch, PgMeta):
1004
1008
  fnames = None
1005
1009
  while True:
1006
1010
  for i in range(bidx, self.ALLCNT):
1007
- if i > bidx and ((i-bidx)%20) == 0:
1011
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
1008
1012
  if self.PGLOG['DSCHECK']:
1009
1013
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
1010
1014
  if metacnt >= self.PGOPT['RSMAX']:
@@ -1050,7 +1054,32 @@ class DsArch(PgArch, PgMeta):
1050
1054
  elif (warch + oarch) == 0 and pgrec['locflag'] == 'B':
1051
1055
  self.pglog(winfo + ": No need Cross {}, Both Web & Object Exist".format(aname), self.PGOPT['wrnlog'])
1052
1056
  continue
1053
- if warch:
1057
+ if cpass == 1: # -AL copy pass: cross copy in a child process; the record pass does the rest
1058
+ if warch or oarch:
1059
+ cdir = self.CPDIRS[i] = 'G' if warch else 'O'
1060
+ self.run_copy(i, self.cross_copy_file, cdir, (afile if warch else ofile), (ofile if warch else afile),
1061
+ bucket, None, self.PGOPT['emerol']|self.OVERRIDE)
1062
+ continue
1063
+ cdone = None
1064
+ if cpass == 2 and i in self.CPDIRS: # cross copied in the copy pass; copy again only if it failed
1065
+ cdir = self.CPDIRS[i]
1066
+ if self.check_cross_copy(i, winfo, cdir, (afile if cdir == 'G' else ofile), bucket, pgrec['data_size']):
1067
+ cdone = cdir
1068
+ elif cdir == 'G':
1069
+ warch = 1
1070
+ else:
1071
+ oarch = 1
1072
+ if cdone == 'G':
1073
+ if aname == 'Move':
1074
+ self.params['LC'][i] = 'G'
1075
+ self.delete_object_file(ofile, bucket, self.PGOPT['extlog'])
1076
+ acnt += 1
1077
+ elif cdone == 'O':
1078
+ if aname == 'Move':
1079
+ self.params['LC'][i] = 'O'
1080
+ self.delete_local_file(afile, self.PGOPT['extlog'])
1081
+ ocnt += 1
1082
+ elif warch:
1054
1083
  if not self.object_copy_local(afile, ofile, bucket, self.PGOPT['emerol']|self.OVERRIDE):
1055
1084
  errcnt += 1
1056
1085
  efiles[i] = 1
@@ -1087,10 +1116,14 @@ class DsArch(PgArch, PgMeta):
1087
1116
  gindex = self.params['GI'][i]
1088
1117
  if gindex != pgrec['gindex'] and (gindex or (self.OPTS['GI'][2]&2) == 0):
1089
1118
  metacnt += self.record_meta_summary('W', dsid, gindex, pgrec['gindex'])
1090
- if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1119
+ if cpass == 1 or errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1091
1120
  perrcnt = errcnt
1092
1121
  self.pglog("Recopy failed {}/{} Web file{} for {}".format(errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
1093
1122
  errcnt = self.reset_errmsg(0)
1123
+ if cpass == 1: # the record pass checks the copies, copies again failed ones and sets the records
1124
+ self.wait_copy_children()
1125
+ self.reset_errmsg(0) # the record pass finds and counts any error again
1126
+ return
1094
1127
  if errcnt:
1095
1128
  self.RETSTAT = self.reset_errmsg(errcnt)
1096
1129
  for i in range(bidx, self.ALLCNT):
@@ -1111,12 +1144,14 @@ class DsArch(PgArch, PgMeta):
1111
1144
  self.reset_rdadb_version(dsid)
1112
1145
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
1113
1146
 
1114
- def crosscopy_help_files(self, aname):
1147
+ def crosscopy_help_files(self, aname, cpass=0):
1115
1148
  """
1116
1149
  Cross-copy (or cross-move) help files between GLADE and the object store.
1117
1150
 
1118
1151
  Args:
1119
1152
  aname -- 'Copy' to copy files, 'Move' to move (deletes the source)
1153
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
1154
+ copy pass and 2 for the record pass (see archive_in_passes())
1120
1155
  """
1121
1156
  tname = 'hfile'
1122
1157
  dsid = self.params['DS']
@@ -1141,7 +1176,7 @@ class DsArch(PgArch, PgMeta):
1141
1176
  fnames = None
1142
1177
  while True:
1143
1178
  for i in range(bidx, self.ALLCNT):
1144
- if i > bidx and ((i-bidx)%20) == 0:
1179
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
1145
1180
  if self.PGLOG['DSCHECK']:
1146
1181
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
1147
1182
  if 'EM' in self.params:
@@ -1197,7 +1232,32 @@ class DsArch(PgArch, PgMeta):
1197
1232
  elif not (harch or oarch) and pgrec['locflag'] == 'B':
1198
1233
  self.pglog(hinfo + ": No need Cross {} Help file, on Both Glade & Object".format(aname), self.PGOPT['wrnlog'])
1199
1234
  continue
1200
- if harch:
1235
+ if cpass == 1: # -AL copy pass: cross copy in a child process; the record pass does the rest
1236
+ if harch or oarch:
1237
+ cdir = self.CPDIRS[i] = 'G' if harch else 'O'
1238
+ self.run_copy(i, self.cross_copy_file, cdir, (afile if harch else ofile), (ofile if harch else afile),
1239
+ bucket, None, self.PGOPT['emerol']|self.OVERRIDE)
1240
+ continue
1241
+ cdone = None
1242
+ if cpass == 2 and i in self.CPDIRS: # cross copied in the copy pass; copy again only if it failed
1243
+ cdir = self.CPDIRS[i]
1244
+ if self.check_cross_copy(i, hinfo, cdir, (afile if cdir == 'G' else ofile), bucket, pgrec['data_size']):
1245
+ cdone = cdir
1246
+ elif cdir == 'G':
1247
+ harch = 1
1248
+ else:
1249
+ oarch = 1
1250
+ if cdone == 'G':
1251
+ if aname == 'Move':
1252
+ self.params['LC'][i] = 'G'
1253
+ self.delete_object_file(ofile, bucket, self.PGOPT['extlog'])
1254
+ acnt += 1
1255
+ elif cdone == 'O':
1256
+ if aname == 'Move':
1257
+ self.params['LC'][i] = 'O'
1258
+ self.delete_local_file(afile, self.PGOPT['extlog'])
1259
+ ocnt += 1
1260
+ elif harch:
1201
1261
  if not self.object_copy_local(afile, ofile, bucket, self.PGOPT['emerol']|self.OVERRIDE):
1202
1262
  errcnt += 1
1203
1263
  efiles[i] = 1
@@ -1224,10 +1284,14 @@ class DsArch(PgArch, PgMeta):
1224
1284
  if not hid:
1225
1285
  self.params['HF'][i] = None
1226
1286
  continue
1227
- if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1287
+ if cpass == 1 or errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1228
1288
  perrcnt = errcnt
1229
1289
  self.pglog("Re{} {} Help file{} for {}".format(aname, errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
1230
1290
  errcnt = self.reset_errmsg(0)
1291
+ if cpass == 1: # the record pass checks the copies, copies again failed ones and sets the records
1292
+ self.wait_copy_children()
1293
+ self.reset_errmsg(0) # the record pass finds and counts any error again
1294
+ return
1231
1295
  if errcnt:
1232
1296
  self.RETSTAT = self.reset_errmsg(errcnt)
1233
1297
  for i in range(bidx, self.ALLCNT):
@@ -1820,13 +1884,17 @@ class DsArch(PgArch, PgMeta):
1820
1884
  self.set_dscheck_dcount(self.ALLCNT, chksize, self.PGOPT['extlog'])
1821
1885
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
1822
1886
 
1823
- def crosscopy_backup_files(self):
1887
+ def crosscopy_backup_files(self, cpass=0):
1824
1888
  """
1825
1889
  Cross-copy Quasar backup files between Globus endpoints.
1826
1890
 
1827
1891
  Transfers backup files between gdex-quasar and gdex-quasar-drdata
1828
1892
  endpoints using Globus transfer, updating RDADB records on completion.
1829
1893
  Sets self.RETSTAT on transfer failures.
1894
+
1895
+ Args:
1896
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
1897
+ copy pass and 2 for the record pass (see archive_in_passes())
1830
1898
  """
1831
1899
  tname = 'bfile'
1832
1900
  qtype = 'D'
@@ -1852,7 +1920,7 @@ class DsArch(PgArch, PgMeta):
1852
1920
  fnames = None
1853
1921
  while True:
1854
1922
  for i in range(bidx, self.ALLCNT):
1855
- if i > bidx and ((i-bidx)%20) == 0:
1923
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
1856
1924
  if self.PGLOG['DSCHECK']:
1857
1925
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
1858
1926
  if 'EM' in self.params:
@@ -1891,7 +1959,26 @@ class DsArch(PgArch, PgMeta):
1891
1959
  elif not (barch or darch) and pgrec['type'] == 'D':
1892
1960
  self.pglog(binfo + ": No need Cross Copy, Both Backup & Drdata Exist", self.PGOPT['wrnlog'])
1893
1961
  continue
1894
- if barch:
1962
+ if cpass == 1: # -AL copy pass: cross copy in a child process; the record pass sets the record
1963
+ if barch or darch:
1964
+ cdir = self.CPDIRS[i] = 'B' if barch else 'D'
1965
+ self.run_copy(i, self.cross_copy_file, cdir, qfile, qfile, (bpoint if barch else dpoint),
1966
+ (dpoint if barch else bpoint), self.PGOPT['emerol']|self.OVERRIDE)
1967
+ continue
1968
+ cdone = None
1969
+ if cpass == 2 and i in self.CPDIRS: # cross copied in the copy pass; copy again only if it failed
1970
+ cdir = self.CPDIRS[i]
1971
+ if self.check_cross_copy(i, binfo, cdir, bfile, (bpoint if cdir == 'B' else dpoint), pgrec['data_size']):
1972
+ cdone = cdir
1973
+ elif cdir == 'B':
1974
+ barch = 1
1975
+ else:
1976
+ darch = 1
1977
+ if cdone == 'B':
1978
+ bcnt += 1
1979
+ elif cdone == 'D':
1980
+ dcnt += 1
1981
+ elif barch:
1895
1982
  bstat = self.endpoint_copy_endpoint(qfile, qfile, bpoint, dpoint, self.PGOPT['emerol']|self.OVERRIDE)
1896
1983
  if bstat == self.FINISH: bstat = self.check_globus_finished(qfile, bpoint, self.PGOPT['emerol']|self.NOWAIT)
1897
1984
  if not bstat:
@@ -1935,26 +2022,34 @@ class DsArch(PgArch, PgMeta):
1935
2022
  self.reset_rdadb_version(dsid)
1936
2023
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
1937
2024
 
1938
- def archive_in_passes(self, archfunc):
2025
+ def archive_in_passes(self, archfunc, *args):
1939
2026
  """
1940
- Run an archive action (archive_web/help/saved_files) once, or in two passes for -AL.
2027
+ Run a file action once, or in two passes for -AL.
1941
2028
 
1942
- With -AL N (N > 1, so PGSIG['MPROC'] > 1) the action runs twice:
1943
- 1. copy pass -- the usual checks, then each file that needs a copy is copied
1944
- in one of up to N child processes (run_copy()); no records are set;
1945
- 2. record pass -- the usual single process loop; a file the copy pass copied
1946
- is checked (check_pass_copies()) instead of copied again, and copied again
1947
- only if its child copy failed; records, dscheck progress and metadata are
1948
- all set here exactly as without -AL.
2029
+ Used for archive_web/help/saved_files, crosscopy_web/help/backup_files and
2030
+ move_web/help/saved/backup_files. With -AL N (N > 1, so PGSIG['MPROC'] > 1)
2031
+ the action runs twice:
2032
+ 1. copy pass -- the usual checks, then the slow file work of each file (its
2033
+ copy, cross copy or moves) runs in one of up to N child processes
2034
+ (run_copy()); no records are set;
2035
+ 2. record pass -- the usual single process loop; work the copy pass did is
2036
+ checked (check_pass_copies(), check_cross_copy(), pass_moved()) instead
2037
+ of done again, and done again only if it failed; source deletes of a
2038
+ cross move, records, dscheck progress and metadata are all done here
2039
+ exactly as without -AL.
1949
2040
 
1950
2041
  Args:
1951
- archfunc -- bound archive method taking the pass number (0, 1 or 2)
2042
+ archfunc -- bound action method taking its own args and then the pass
2043
+ number (0, 1 or 2) as keyword cpass
2044
+ args -- arguments of archfunc before cpass, such as 'Copy'/'Move'
1952
2045
  """
1953
- if self.PGSIG['MPROC'] < 2: return archfunc()
2046
+ if self.PGSIG['MPROC'] < 2: return archfunc(*args)
1954
2047
  self.CPFILES = {}
1955
- archfunc(1)
1956
- archfunc(2)
2048
+ self.CPDIRS = {}
2049
+ archfunc(*args, cpass=1)
2050
+ archfunc(*args, cpass=2)
1957
2051
  self.CPFILES = {}
2052
+ self.CPDIRS = {}
1958
2053
 
1959
2054
  def run_copy(self, i, copyfunc, *args):
1960
2055
  """
@@ -2026,6 +2121,133 @@ class DsArch(PgArch, PgMeta):
2026
2121
  if not self.local_copy_object(ofile, lfile, bucket, None, logact): return 0
2027
2122
  return 1
2028
2123
 
2124
+ def child_copy_failed(self, i):
2125
+ """
2126
+ Return true if the -AL copy pass work of file *i* is known to have failed.
2127
+
2128
+ self.CPFILES[i] holds the child's process name, whose exit code is then in
2129
+ self.CEXITS, or 1/0 for work done in the parent; unknown counts as not failed.
2130
+ """
2131
+ cstat = self.CPFILES.get(i)
2132
+ if isinstance(cstat, str): # done in a child: failed if it exited with an error
2133
+ ecode = self.CEXITS.get(cstat)
2134
+ return ecode is not None and ecode != 0
2135
+ return cstat == 0 # done in the parent: 1 or 0
2136
+
2137
+ def cross_copy_file(self, cdir, tofile, fromfile, topoint, frompoint, logact):
2138
+ """
2139
+ Cross copy one file in the given direction; run in a copy pass child.
2140
+
2141
+ Args:
2142
+ cdir -- 'G' object store to GLADE, 'O' GLADE to object store,
2143
+ 'B'/'D' Globus copy to the Quasar backup/drdata endpoint
2144
+ tofile -- target path, object key or Quasar file
2145
+ fromfile -- source path, object key or Quasar file
2146
+ topoint -- object store bucket for 'G'/'O', target endpoint for 'B'/'D'
2147
+ frompoint -- source endpoint for 'B'/'D', None otherwise
2148
+ logact -- logging flags
2149
+
2150
+ Returns:
2151
+ a true value if the copy succeeded (Globus copies wait for their task)
2152
+ """
2153
+ if cdir == 'G': return self.object_copy_local(tofile, fromfile, topoint, logact)
2154
+ if cdir == 'O': return self.local_copy_object(tofile, fromfile, topoint, None, logact)
2155
+ stat = self.endpoint_copy_endpoint(tofile, fromfile, topoint, frompoint, logact)
2156
+ if stat == self.FINISH: stat = self.check_globus_finished(tofile, topoint, logact|self.NOWAIT)
2157
+ return stat
2158
+
2159
+ def check_cross_copy(self, i, finfo, cdir, tofile, topoint, size):
2160
+ """
2161
+ Check a cross copy, upload or download the -AL copy pass made, for the record pass.
2162
+
2163
+ Args:
2164
+ i -- file index, the key of self.CPFILES
2165
+ finfo -- file description, for the message
2166
+ cdir -- copy direction, see cross_copy_file()
2167
+ tofile -- target path, object key or Quasar file
2168
+ topoint -- object store bucket or target endpoint
2169
+ size -- size in the file record, 0 to skip the size check
2170
+
2171
+ Returns:
2172
+ 1 if the target holds the file, 0 if the copy is to be made again
2173
+ """
2174
+ if not self.child_copy_failed(i):
2175
+ if cdir == 'G':
2176
+ info = self.check_local_file(tofile, 0, self.PGOPT['emerol']|self.PFSIZE)
2177
+ elif cdir == 'O':
2178
+ info = self.check_object_file(tofile, topoint, 0, self.PGOPT['emerol'])
2179
+ else:
2180
+ info = self.check_backup_file(tofile, topoint, 0, self.PGOPT['emerol'])
2181
+ if info and (not size or info['data_size'] == size): return 1
2182
+ self.pglog("{}: copy in child process failed, copy again".format(finfo), self.PGOPT['wrnlog'])
2183
+ return 0
2184
+
2185
+ def do_move(self, mtype, tofile, fromfile, where, logact):
2186
+ """
2187
+ Do one step of a file's move: a move, an upload/download or a delete.
2188
+
2189
+ Args:
2190
+ mtype -- 'L' move a GLADE file, 'O' move an object, 'B' move a Quasar
2191
+ backup file, 'U' upload a GLADE file to the object store,
2192
+ 'G' download an object to GLADE, 'X' delete an object,
2193
+ 'Y' delete a GLADE file
2194
+ tofile -- new path, object key or Quasar file (None for a delete)
2195
+ fromfile -- old path, object key or Quasar file
2196
+ where -- bucket for 'O'/'U'/'G'/'X', endpoint for 'B', None otherwise
2197
+ logact -- logging flags
2198
+
2199
+ Returns:
2200
+ a true value if done (a move also when it was moved already)
2201
+ """
2202
+ if mtype == 'L': return self.move_local_file(tofile, fromfile, logact)
2203
+ if mtype == 'O': return self.move_object_file(tofile, fromfile, where, where, logact)
2204
+ if mtype == 'B': return self.move_backup_file(tofile, fromfile, where, logact)
2205
+ if mtype == 'U': return self.local_copy_object(tofile, fromfile, where, None, logact)
2206
+ if mtype == 'G': return self.object_copy_local(tofile, fromfile, where, logact)
2207
+ if mtype == 'X': return self.delete_object_file(fromfile, where, logact)
2208
+ return self.delete_local_file(fromfile, logact)
2209
+
2210
+ def move_pass_files(self, moves, logact):
2211
+ """
2212
+ Do the moves of one file in order; run in a copy pass child.
2213
+
2214
+ Args:
2215
+ moves -- list of (mtype, tofile, fromfile, where) steps, see do_move()
2216
+ logact -- logging flags
2217
+
2218
+ Returns:
2219
+ 1 if all moves succeeded, 0 at the first failure
2220
+ """
2221
+ for (mtype, tofile, fromfile, where) in moves:
2222
+ if not self.do_move(mtype, tofile, fromfile, where, logact): return 0
2223
+ return 1
2224
+
2225
+ def move_one(self, i, mtype, tofile, fromfile, where, logact):
2226
+ """
2227
+ Move one file in the record pass, or the only pass without -AL.
2228
+
2229
+ When the -AL copy pass already moved it (target there, source gone) the
2230
+ move counts as done without another call; otherwise do_move() moves it,
2231
+ which also finishes a move that failed in its child process.
2232
+ """
2233
+ if i in self.CPFILES and self.pass_moved(mtype, tofile, fromfile, where): return 1
2234
+ return self.do_move(mtype, tofile, fromfile, where, logact)
2235
+
2236
+ def pass_moved(self, mtype, tofile, fromfile, where):
2237
+ """
2238
+ Return true if the target of a move exists and its source is gone, checked quietly.
2239
+
2240
+ The source counts as gone only when its check finds no file (None), not
2241
+ when the check itself fails (FAILURE), so an error leads to a real move.
2242
+ """
2243
+ if mtype == 'L':
2244
+ check = lambda f: self.check_local_file(f, 0, 0)
2245
+ elif mtype == 'O':
2246
+ check = lambda f: self.check_object_file(f, where, 0, 0)
2247
+ else:
2248
+ check = lambda f: self.check_backup_file(f, where, 0, 0)
2249
+ return bool(check(tofile)) and check(fromfile) is None
2250
+
2029
2251
  def check_pass_copies(self, i, fname, afile, ofile, bucket, lsize):
2030
2252
  """
2031
2253
  Check the targets the -AL copy pass copied a file to, for the record pass.
@@ -2047,13 +2269,7 @@ class DsArch(PgArch, PgMeta):
2047
2269
  a 0 for a target given means its copy failed and is to be copied again
2048
2270
  """
2049
2271
  gdone = odone = 0
2050
- cstat = self.CPFILES.get(i)
2051
- if isinstance(cstat, str): # copied in a child: failed if it exited with an error
2052
- ecode = self.CEXITS.get(cstat)
2053
- failed = ecode is not None and ecode != 0
2054
- else: # copied in the parent: 1 or 0
2055
- failed = cstat == 0
2056
- if failed:
2272
+ if self.child_copy_failed(i):
2057
2273
  self.pglog("{}: copy in child process failed, copy again".format(fname), self.PGOPT['wrnlog'])
2058
2274
  return (0, 0)
2059
2275
  if afile:
@@ -3268,7 +3484,7 @@ class DsArch(PgArch, PgMeta):
3268
3484
  self.ADDCNT += 1
3269
3485
  return bid
3270
3486
 
3271
- def move_web_files(self):
3487
+ def move_web_files(self, cpass=0):
3272
3488
  """
3273
3489
  Rename or relocate web files on disk and update their RDADB records.
3274
3490
 
@@ -3276,6 +3492,11 @@ class DsArch(PgArch, PgMeta):
3276
3492
  type changes (e.g. D→N). Supports -TS (to-saved) conversions and
3277
3493
  updates backup file notes via save_move_info().
3278
3494
  Sets self.RETSTAT on unresolved failures.
3495
+
3496
+ Args:
3497
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
3498
+ move pass (moves in up to N child processes, no records) and then 2
3499
+ for the record pass (see archive_in_passes())
3279
3500
  """
3280
3501
  tname = 'wfile'
3281
3502
  dsid = self.params['DS']
@@ -3341,7 +3562,7 @@ class DsArch(PgArch, PgMeta):
3341
3562
  self.validate_multiple_values(tname, self.ALLCNT, fnames)
3342
3563
  reorder = metatotal = metacnt = wcnt = ocnt = self.MODCNT = 0
3343
3564
  for i in range(bidx, self.ALLCNT):
3344
- if i > bidx and ((i-bidx)%20) == 0:
3565
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
3345
3566
  if metacnt > self.PGOPT['RSMAX']:
3346
3567
  metatotal += self.process_metadata("W", metacnt, self.PGOPT['emerol'])
3347
3568
  metacnt = 0
@@ -3381,8 +3602,14 @@ class DsArch(PgArch, PgMeta):
3381
3602
  wmove = 0
3382
3603
  elif pgrec['locflag'] == 'G':
3383
3604
  omove = 0
3605
+ if cpass == 1: # -AL move pass: move the files in a child process; the record pass sets the record
3606
+ moves = []
3607
+ if wmove and aolds[i] != anews[i]: moves.append(('L', anews[i], aolds[i], None))
3608
+ if omove and oolds[i] != onews[i]: moves.append(('O', onews[i], oolds[i], bucket))
3609
+ if moves: self.run_copy(i, self.move_pass_files, moves, self.PGOPT['emerol']|self.OVERRIDE)
3610
+ continue
3384
3611
  if wmove and aolds[i] != anews[i]:
3385
- ret = self.move_local_file(anews[i], aolds[i], self.PGOPT['emerol']|self.OVERRIDE)
3612
+ ret = self.move_one(i, 'L', anews[i], aolds[i], None, self.PGOPT['emerol']|self.OVERRIDE)
3386
3613
  if not ret:
3387
3614
  self.RETSTAT = 1
3388
3615
  continue
@@ -3390,7 +3617,7 @@ class DsArch(PgArch, PgMeta):
3390
3617
  wcnt += 1
3391
3618
  self.set_web_move(pgrec)
3392
3619
  if omove and oolds[i] != onews[i]:
3393
- if not self.move_object_file(onews[i], oolds[i], bucket, bucket, self.PGOPT['emerol']|self.OVERRIDE):
3620
+ if not self.move_one(i, 'O', onews[i], oolds[i], bucket, self.PGOPT['emerol']|self.OVERRIDE):
3394
3621
  self.RETSTAT = 1
3395
3622
  continue
3396
3623
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
@@ -3405,6 +3632,10 @@ class DsArch(PgArch, PgMeta):
3405
3632
  gindex = self.params['GI'][i]
3406
3633
  if gindex != pgrec['gindex'] and (gindex or (self.OPTS['GI'][2]&2) == 0):
3407
3634
  metacnt += self.record_meta_summary('W', dsid, gindex, pgrec['gindex'])
3635
+ if cpass == 1: # the record pass checks the moves, finishes failed ones and sets the records
3636
+ self.wait_copy_children()
3637
+ self.reset_errmsg(0) # the record pass finds and counts any error again
3638
+ return
3408
3639
  self.pglog("{}/{}/{}, Disk/Object/Record, of {} Web file{} moved".format(wcnt, ocnt, self.MODCNT, self.ALLCNT, s), self.PGOPT['emllog'])
3409
3640
  if metacnt > 0:
3410
3641
  metatotal += self.process_metadata('W', metacnt, self.PGOPT['emerol'])
@@ -3418,12 +3649,17 @@ class DsArch(PgArch, PgMeta):
3418
3649
  self.reset_rdadb_version(self.params['OD'])
3419
3650
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ''
3420
3651
 
3421
- def move_help_files(self):
3652
+ def move_help_files(self, cpass=0):
3422
3653
  """
3423
3654
  Rename or relocate help files on disk and update their RDADB records.
3424
3655
 
3425
3656
  Handles renaming within the same dataset and cross-dataset moves, along
3426
3657
  with any type changes. Sets self.RETSTAT on unresolved failures.
3658
+
3659
+ Args:
3660
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
3661
+ move pass (moves in up to N child processes, no records) and then 2
3662
+ for the record pass (see archive_in_passes())
3427
3663
  """
3428
3664
  tname = 'hfile'
3429
3665
  dsid = self.params['DS']
@@ -3481,7 +3717,7 @@ class DsArch(PgArch, PgMeta):
3481
3717
  self.validate_multiple_values(tname, self.ALLCNT, fnames)
3482
3718
  reorder = hcnt = ocnt = self.MODCNT = 0
3483
3719
  for i in range(bidx, self.ALLCNT):
3484
- if i > bidx and ((i-bidx)%20) == 0:
3720
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
3485
3721
  if self.PGLOG['DSCHECK']:
3486
3722
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
3487
3723
  if 'EM' in self.params:
@@ -3504,20 +3740,30 @@ class DsArch(PgArch, PgMeta):
3504
3740
  hmove = 0
3505
3741
  elif pgrec['locflag'] == 'G':
3506
3742
  omove = 0
3743
+ if cpass == 1: # -AL move pass: move the files in a child process; the record pass sets the record
3744
+ moves = []
3745
+ if hmove and aolds[i] != anews[i]: moves.append(('L', anews[i], aolds[i], None))
3746
+ if omove and oolds[i] != onews[i]: moves.append(('O', onews[i], oolds[i], bucket))
3747
+ if moves: self.run_copy(i, self.move_pass_files, moves, self.PGOPT['emerol']|self.OVERRIDE)
3748
+ continue
3507
3749
  if hmove and aolds[i] != anews[i]:
3508
- ret = self.move_local_file(anews[i], aolds[i], self.PGOPT['emerol']|self.OVERRIDE)
3750
+ ret = self.move_one(i, 'L', anews[i], aolds[i], None, self.PGOPT['emerol']|self.OVERRIDE)
3509
3751
  if not ret:
3510
3752
  self.RETSTAT = 1
3511
3753
  continue
3512
3754
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
3513
3755
  hcnt += 1
3514
3756
  if omove and oolds[i] != onews[i]:
3515
- if not self.move_object_file(onews[i], oolds[i], bucket, bucket, self.PGOPT['emerol']|self.OVERRIDE):
3757
+ if not self.move_one(i, 'O', onews[i], oolds[i], bucket, self.PGOPT['emerol']|self.OVERRIDE):
3516
3758
  self.RETSTAT = 1
3517
3759
  continue
3518
3760
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
3519
3761
  ocnt += 1
3520
3762
  if not self.set_one_helpfile(i, pgrec, hnews[i], fnames, tnews[i], None, tmpds): continue
3763
+ if cpass == 1: # the record pass checks the moves, finishes failed ones and sets the records
3764
+ self.wait_copy_children()
3765
+ self.reset_errmsg(0) # the record pass finds and counts any error again
3766
+ return
3521
3767
  self.pglog("{}/{}/{}/{}, Disk/Object/Record, of {} Help file{} moved".format(hcnt, ocnt, self.MODCNT, self.ALLCNT, s), self.PGOPT['emllog'])
3522
3768
  if self.PGLOG['DSCHECK']: self.set_dscheck_dcount(self.ALLCNT, 0, self.PGOPT['extlog'])
3523
3769
  if 'ON' in self.params: reorder = self.reorder_files(dsid, self.params['ON'], tname)
@@ -3527,7 +3773,7 @@ class DsArch(PgArch, PgMeta):
3527
3773
  self.reset_rdadb_version(self.params['OD'])
3528
3774
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ''
3529
3775
 
3530
- def saved_to_web_files(self):
3776
+ def saved_to_web_files(self, cpass=0):
3531
3777
  """
3532
3778
  Convert saved files to web files (-TS action).
3533
3779
 
@@ -3535,6 +3781,11 @@ class DsArch(PgArch, PgMeta):
3535
3781
  store), creates a new wfile RDADB record, and removes the old sfile
3536
3782
  record. Preserves backup links by updating bfile.note via save_move_info().
3537
3783
  Sets self.RETSTAT on unresolved failures.
3784
+
3785
+ Args:
3786
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
3787
+ move pass (moves in up to N child processes, no records) and then 2
3788
+ for the record pass (see archive_in_passes())
3538
3789
  """
3539
3790
  tname = 'wfile'
3540
3791
  dsid = self.params['DS']
@@ -3612,7 +3863,7 @@ class DsArch(PgArch, PgMeta):
3612
3863
  fnames = None
3613
3864
  reorder = metatotal = metacnt = wcnt = ocnt = dcnt = self.ADDCNT = self.MODCNT = 0
3614
3865
  for i in range(bidx, self.ALLCNT):
3615
- if i > bidx and ((i-bidx)%20) == 0:
3866
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
3616
3867
  if metacnt > self.PGOPT['RSMAX']:
3617
3868
  metatotal += self.process_metadata("W", metacnt, self.PGOPT['emerol'])
3618
3869
  metacnt = 0
@@ -3650,8 +3901,16 @@ class DsArch(PgArch, PgMeta):
3650
3901
  elif locflag == 'G':
3651
3902
  omove = 0
3652
3903
  dslocflags.add('G')
3904
+ if cpass == 1: # -AL move pass: move the file in a child process; the record pass sets the record
3905
+ moves = []
3906
+ if wmove: moves.append(('L', anews[i], aolds[i], None))
3907
+ if omove: moves.append(('U', onews[i], (anews[i] if wmove else aolds[i]), tobucket))
3908
+ if omove and not wmove: moves.append(('Y', None, aolds[i], None))
3909
+ if moves: self.run_copy(i, self.move_pass_files, moves, self.PGOPT['emerol']|self.OVERRIDE)
3910
+ continue
3911
+ inpass = cpass == 2 and i in self.CPFILES
3653
3912
  if wmove:
3654
- stat = self.move_local_file(anews[i], aolds[i], self.PGOPT['emerol']|self.OVERRIDE)
3913
+ stat = self.move_one(i, 'L', anews[i], aolds[i], None, self.PGOPT['emerol']|self.OVERRIDE)
3655
3914
  sfrom = 0
3656
3915
  if not stat:
3657
3916
  self.RETSTAT = 1
@@ -3659,13 +3918,18 @@ class DsArch(PgArch, PgMeta):
3659
3918
  wcnt += 1
3660
3919
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
3661
3920
  if omove:
3662
- stat = self.local_copy_object(onews[i], aolds[i], tobucket, None, self.PGOPT['emerol']|self.OVERRIDE)
3921
+ # upload from the new web path once the saved file was moved there
3922
+ if inpass and self.check_cross_copy(i, solds[i], 'O', onews[i], tobucket, pgrec['data_size']):
3923
+ stat = 1 # uploaded in the move pass
3924
+ else:
3925
+ stat = self.local_copy_object(onews[i], (aolds[i] if sfrom else anews[i]), tobucket, None, self.PGOPT['emerol']|self.OVERRIDE)
3663
3926
  if not stat:
3664
3927
  self.RETSTAT = 1
3665
3928
  continue
3666
3929
  ocnt += 1
3667
3930
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
3668
- if sfrom: self.delete_local_file(aolds[i], self.PGOPT['emerol'])
3931
+ if sfrom and (not inpass or self.check_local_file(aolds[i], 0, 0)):
3932
+ self.delete_local_file(aolds[i], self.PGOPT['emerol'])
3669
3933
  if pgrec['bid'] and not self.params['QF'][i]: self.params['QF'][i] = pgrec['bid']
3670
3934
  if pgrec['gindex'] and not self.params['GI'][i]: self.params['GI'][i] = pgrec['gindex']
3671
3935
  if pgrec['data_size'] and not self.params['SZ'][i]: self.params['SZ'][i] = pgrec['data_size']
@@ -3683,6 +3947,10 @@ class DsArch(PgArch, PgMeta):
3683
3947
  if 'GX' in self.params and self.PGOPT['GXTYP'].find(type) > -1:
3684
3948
  metacnt += self.record_meta_gather('W', dsid, wnews[i], self.params['DF'][i])
3685
3949
  self.cache_meta_tindex(dsid, wid, 'W')
3950
+ if cpass == 1: # the record pass checks the moves, finishes failed ones and sets the records
3951
+ self.wait_copy_children()
3952
+ self.reset_errmsg(0) # the record pass finds and counts any error again
3953
+ return
3686
3954
  self.pglog("{}/{}/{}/{}, Disk/Object/RecordDelete/RecordAdd, of {} Saved file{} moved".format(wcnt, ocnt, dcnt, self.ADDCNT, self.ALLCNT, s), self.PGOPT['emllog'])
3687
3955
  if metacnt > 0:
3688
3956
  metatotal += self.process_metadata('W', metacnt, self.PGOPT['emerol'])
@@ -3890,12 +4158,17 @@ class DsArch(PgArch, PgMeta):
3890
4158
  if (dcnt + reorder) > 0: self.reset_rdadb_version(dsid)
3891
4159
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
3892
4160
 
3893
- def move_saved_files(self):
4161
+ def move_saved_files(self, cpass=0):
3894
4162
  """
3895
4163
  Rename or relocate saved files on disk and update their RDADB records.
3896
4164
 
3897
4165
  Handles renaming within the same dataset and cross-dataset moves, along
3898
4166
  with any type changes. Sets self.RETSTAT on unresolved failures.
4167
+
4168
+ Args:
4169
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
4170
+ move pass (moves in up to N child processes, no records) and then 2
4171
+ for the record pass (see archive_in_passes())
3899
4172
  """
3900
4173
  tname = 'sfile'
3901
4174
  dsid = self.params['DS']
@@ -3957,7 +4230,7 @@ class DsArch(PgArch, PgMeta):
3957
4230
  chksize = self.PGLOG['DSCHECK']['size']
3958
4231
  reorder = self.MODCNT = scnt = 0
3959
4232
  for i in range(bidx, self.ALLCNT):
3960
- if i > bidx and ((i-bidx)%20) == 0:
4233
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
3961
4234
  if self.PGLOG['DSCHECK']:
3962
4235
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
3963
4236
  if 'EM' in self.params:
@@ -3986,14 +4259,23 @@ class DsArch(PgArch, PgMeta):
3986
4259
  not self.pgget("dsgroup", "", "{} and gindex = {}".format(dcnd, pgrec['gindex']), self.PGOPT['extlog'])):
3987
4260
  self.pglog("Group Index {} is not in RDADB for {}\n".format(pgrec['gindex'], dsid) +
3988
4261
  "Specify Original/New group index via options -OG/-GI", self.PGOPT['extlog'])
4262
+ if cpass == 1: # -AL move pass: move the files in a child process; the record pass sets the record
4263
+ moves = []
4264
+ if aolds[i] != anews[i]: moves.append(('L', anews[i], aolds[i], None))
4265
+ if moves: self.run_copy(i, self.move_pass_files, moves, self.PGOPT['emerol']|self.OVERRIDE)
4266
+ continue
3989
4267
  if aolds[i] != anews[i]:
3990
- if not self.move_local_file(anews[i], aolds[i], self.PGOPT['emerol']|self.OVERRIDE):
4268
+ if not self.move_one(i, 'L', anews[i], aolds[i], None, self.PGOPT['emerol']|self.OVERRIDE):
3991
4269
  self.RETSTAT = 1
3992
4270
  continue
3993
4271
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
3994
4272
  scnt += 1
3995
4273
  self.set_one_savedfile(i, pgrec, snews[i], fnames, tnews[i], None, tmpds)
3996
4274
  if pgrec['bid']: self.save_move_info(pgrec['bid'], solds[i], type, 'S', self.params['OD'], snews[i], tnews[i], 'S', dsid)
4275
+ if cpass == 1: # the record pass checks the moves, finishes failed ones and sets the records
4276
+ self.wait_copy_children()
4277
+ self.reset_errmsg(0) # the record pass finds and counts any error again
4278
+ return
3997
4279
  self.pglog("{}/{}, Disk/Record, of {} Saved file{} moved".format(scnt, self.MODCNT, self.ALLCNT, s), self.PGOPT['emllog'])
3998
4280
  if self.PGLOG['DSCHECK']:
3999
4281
  self.set_dscheck_dcount(self.ALLCNT, chksize, self.PGOPT['extlog'])
@@ -4005,13 +4287,18 @@ class DsArch(PgArch, PgMeta):
4005
4287
  self.reset_rdadb_version(self.params['OD'])
4006
4288
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
4007
4289
 
4008
- def move_backup_files(self):
4290
+ def move_backup_files(self, cpass=0):
4009
4291
  """
4010
4292
  Move Quasar backup files to a different dataset and update RDADB records.
4011
4293
 
4012
4294
  Transfers ownership of bfile records to the target dataset (-OD) and
4013
4295
  updates the backup archive note via save_move_info().
4014
4296
  Sets self.RETSTAT on failures.
4297
+
4298
+ Args:
4299
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
4300
+ move pass (moves in up to N child processes, no records) and then 2
4301
+ for the record pass (see archive_in_passes())
4015
4302
  """
4016
4303
  s = 's' if self.ALLCNT > 1 else ''
4017
4304
  tname = 'bfile'
@@ -4060,7 +4347,7 @@ class DsArch(PgArch, PgMeta):
4060
4347
  chksize = self.PGLOG['DSCHECK']['size']
4061
4348
  reorder = self.MODCNT = bcnt = dcnt = 0
4062
4349
  for i in range(bidx, self.ALLCNT):
4063
- if i > bidx and ((i-bidx)%20) == 0:
4350
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
4064
4351
  if self.PGLOG['DSCHECK']:
4065
4352
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
4066
4353
  if 'EM' in self.params:
@@ -4083,20 +4370,31 @@ class DsArch(PgArch, PgMeta):
4083
4370
  continue
4084
4371
  dmove = bmove = 1
4085
4372
  if type == 'B': dmove = 0
4373
+ if cpass == 1: # -AL move pass: move the files in a child process; the record pass sets the record
4374
+ moves = []
4375
+ if qolds[i] != qnews[i]:
4376
+ if bmove: moves.append(('B', qnews[i], qolds[i], bkend))
4377
+ if dmove: moves.append(('B', qnews[i], qolds[i], drend))
4378
+ if moves: self.run_copy(i, self.move_pass_files, moves, self.PGOPT['emerol']|self.OVERRIDE)
4379
+ continue
4086
4380
  if qolds[i] != qnews[i]:
4087
4381
  if bmove:
4088
- if not self.move_backup_file(qnews[i], qolds[i], bkend, self.PGOPT['emerol']|self.OVERRIDE):
4382
+ if not self.move_one(i, 'B', qnews[i], qolds[i], bkend, self.PGOPT['emerol']|self.OVERRIDE):
4089
4383
  self.RETSTAT = 1
4090
4384
  continue
4091
4385
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
4092
4386
  bcnt += 1
4093
4387
  if dmove:
4094
- if not self.move_backup_file(qnews[i], qolds[i], drend, self.PGOPT['emerol']|self.OVERRIDE):
4388
+ if not self.move_one(i, 'B', qnews[i], qolds[i], drend, self.PGOPT['emerol']|self.OVERRIDE):
4095
4389
  self.RETSTAT = 1
4096
4390
  continue
4097
4391
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
4098
4392
  dcnt += 1
4099
4393
  self.set_one_backfile(i, pgrec, bnews[i], fnames, tnews[i], tmpds)
4394
+ if cpass == 1: # the record pass checks the moves, finishes failed ones and sets the records
4395
+ self.wait_copy_children()
4396
+ self.reset_errmsg(0) # the record pass finds and counts any error again
4397
+ return
4100
4398
  self.pglog("{}/{}/{}, Quasar/Drdata/Record, of {} Backup file{} moved".format(bcnt, dcnt, self.MODCNT, self.ALLCNT, s), self.PGOPT['emllog'])
4101
4399
  if self.PGLOG['DSCHECK']:
4102
4400
  self.set_dscheck_dcount(self.ALLCNT, chksize, self.PGOPT['extlog'])
@@ -4108,7 +4406,7 @@ class DsArch(PgArch, PgMeta):
4108
4406
  self.reset_rdadb_version(self.params['OD'])
4109
4407
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
4110
4408
 
4111
- def web_to_saved_files(self):
4409
+ def web_to_saved_files(self, cpass=0):
4112
4410
  """
4113
4411
  Convert web files to saved files (-TW action).
4114
4412
 
@@ -4116,6 +4414,11 @@ class DsArch(PgArch, PgMeta):
4116
4414
  new sfile RDADB record, and removes the old wfile record. Optionally
4117
4415
  also deletes the file from the object store. Preserves backup links.
4118
4416
  Sets self.RETSTAT on unresolved failures.
4417
+
4418
+ Args:
4419
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
4420
+ move pass (moves in up to N child processes, no records) and then 2
4421
+ for the record pass (see archive_in_passes())
4119
4422
  """
4120
4423
  s = 's' if self.ALLCNT > 1 else ''
4121
4424
  tname = 'sfile'
@@ -4193,7 +4496,7 @@ class DsArch(PgArch, PgMeta):
4193
4496
  fnames = None
4194
4497
  metatotal = metacnt = reorder = wcnt = ocnt = dcnt = self.ADDCNT = self.MODCNT = 0
4195
4498
  for i in range(bidx, self.ALLCNT):
4196
- if i > bidx and ((i-bidx)%20) == 0:
4499
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
4197
4500
  if metacnt >= self.PGOPT['RSMAX']:
4198
4501
  metatotal += self.process_meta_delete("W", self.PGOPT['emerol'])
4199
4502
  metacnt = 0
@@ -4225,19 +4528,29 @@ class DsArch(PgArch, PgMeta):
4225
4528
  wfrom = 0
4226
4529
  elif locflag == 'G':
4227
4530
  ofrom = 0
4531
+ if cpass == 1: # -AL move pass: move the file in a child process; the record pass sets the record
4532
+ moves = [('L', anews[i], aolds[i], None) if wfrom else ('G', anews[i], oolds[i], frombucket)]
4533
+ if ofrom: moves.append(('X', None, oolds[i], frombucket))
4534
+ self.run_copy(i, self.move_pass_files, moves, self.PGOPT['emerol']|self.OVERRIDE)
4535
+ continue
4536
+ inpass = cpass == 2 and i in self.CPFILES
4228
4537
  if wfrom:
4229
- stat = self.move_local_file(anews[i], aolds[i], self.PGOPT['emerol']|self.OVERRIDE)
4538
+ stat = self.move_one(i, 'L', anews[i], aolds[i], None, self.PGOPT['emerol']|self.OVERRIDE)
4230
4539
  wfrom = 0
4231
4540
  wcnt += 1
4232
4541
  else:
4233
- stat = self.object_copy_local(anews[i], oolds[i], frombucket, self.PGOPT['emerol']|self.OVERRIDE)
4542
+ if inpass and self.check_cross_copy(i, wolds[i], 'G', anews[i], None, pgrec['data_size']):
4543
+ stat = 1 # downloaded in the move pass
4544
+ else:
4545
+ stat = self.object_copy_local(anews[i], oolds[i], frombucket, self.PGOPT['emerol']|self.OVERRIDE)
4234
4546
  ocnt += 1
4235
4547
  if not stat:
4236
4548
  self.RETSTAT = 1
4237
4549
  continue
4238
4550
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
4239
4551
  if wfrom: self.delete_local_file(aolds[i], self.PGOPT['emerol'])
4240
- if ofrom: self.delete_object_file(oolds[i], frombucket, self.PGOPT['emerol'])
4552
+ if ofrom and (not inpass or self.check_object_file(oolds[i], frombucket, 0, 0)):
4553
+ self.delete_object_file(oolds[i], frombucket, self.PGOPT['emerol'])
4241
4554
  if self.PGOPT['GXTYP'].find(type) > -1 and ('DX' in self.params or pgrec and pgrec['meta_link'] and pgrec['meta_link'] != 'N' and 'KM' not in self.params):
4242
4555
  metacnt += self.record_meta_delete('W', dsid, pgrec['wfile'])
4243
4556
  if pgrec['bid'] and not self.params['QF'][i]: self.params['QF'][i] = pgrec['bid']
@@ -4254,6 +4567,10 @@ class DsArch(PgArch, PgMeta):
4254
4567
  info = self.get_file_origin_info(snews[i], pgrec)
4255
4568
  self.set_one_savedfile(i, None, snews[i], fnames, tnews[i], info, dsid, 12)
4256
4569
  if pgrec['bid']: self.save_move_info(pgrec['bid'], wolds[i], type, 'W', self.params['OD'], snews[i], tnews[i], 'S', dsid)
4570
+ if cpass == 1: # the record pass checks the moves, finishes failed ones and sets the records
4571
+ self.wait_copy_children()
4572
+ self.reset_errmsg(0) # the record pass finds and counts any error again
4573
+ return
4257
4574
  self.pglog("{}/{}/{}/{}, Disk/Object/RecordDelete/RecordAdd, of {} Web file{} moved".format(wcnt, ocnt, dcnt, self.ADDCNT, self.ALLCNT, s), self.PGOPT['emllog'])
4258
4575
  if metacnt > 0:
4259
4576
  metatotal += self.process_meta_delete('W', self.PGOPT['emerol'])
@@ -1992,15 +1992,17 @@ They come in two forms:
1992
1992
  value or more than one causes an error.
1993
1993
 
1994
1994
  -AL or -AsyncLimit (Alias: -AsynchronousLimit), the number of
1995
- processes that run at the same time. For -AW, -AH and -AS (not with
1996
- -XC/-XM), up to this many child processes copy the files in a first
1997
- pass; a second pass then checks each copy, copies again any that
1998
- failed, and sets the file records as usual. It also caps the
1999
- 'gatherxml' and 'dcm' calls run in the background. Cross copies
2000
- (-XC/-XM) and -AQ run one file at a time. Works with -BP (-d) too:
2001
- the PBS batch job then gets one cpu and 1gb of memory per process
2002
- (select=1:ncpus=N:mem=Ngb) unless -QS already requests select,
2003
- ncpus or mem.
1995
+ processes that run at the same time. For -AW, -AH and -AS (also with
1996
+ -XC/-XM, and -AQ -XC) and for -MV (also with -TS/-TW), up to this
1997
+ many child processes copy, cross copy or move the files in a first
1998
+ pass; a second pass then checks each one, redoes any that failed,
1999
+ deletes the sources of a cross move (-XM), and sets the file records
2000
+ as usual. It also caps the 'gatherxml' and 'dcm' calls run in the
2001
+ background, such as for -GX with -AW or -SW. Other actions, such as
2002
+ -SS, run one process and ignore it. Works with -BP (-d) too: when
2003
+ the action can use it, the PBS batch job gets one cpu and 1gb of
2004
+ memory per process (select=1:ncpus=N:mem=Ngb) unless -QS already
2005
+ requests select, ncpus or mem.
2004
2006
 
2005
2007
  -AO or -ActOption (Alias: -ActionOption), the token that marks
2006
2008
  Action and Mode options in input files. Defaults to '<!>'. Change
@@ -699,7 +699,7 @@ class PgArch(PgOPT, PgCMD, PgSplit):
699
699
  # set command line Batch options
700
700
  self.set_qsub_walltime()
701
701
  self.set_batch_options(self.params, 1)
702
- self.set_qsub_resources() # after -QS, so a user resource request is kept
702
+ self.set_qsub_resources(acts) # after -QS, so a user resource request is kept
703
703
  self.init_dscheck(0, '', "dsarch", self.params['DS'], cact, self.PGLOG['CURDIR'], self.params['LN'],
704
704
  self.params['BP'], self.PGOPT['emerol'])
705
705
  if 'EM' in self.params: self.PGOPT['emlerr'] = self.LOGERR|self.EMEROL
@@ -738,19 +738,43 @@ class PgArch(PgOPT, PgCMD, PgSplit):
738
738
  self.set_one_boption('qoptions', '-l walltime={}:00:00'.format(hr), 1)
739
739
  return
740
740
 
741
- def set_qsub_resources(self):
741
+ def uses_async_limit(self, acts):
742
742
  """
743
- Request PBS cpus and memory for the -AL background processes of a -d batch job.
743
+ Return true if action *acts* with the current options can run -AL processes.
744
744
 
745
- With -AL N (N > 1) the batch job runs up to N background copies at once,
746
- so its PBS chunk gets select=1:ncpus=N:mem=Ngb (1gb per process), added
747
- to the existing '-l' list of the qsub options (after any walltime) or as
748
- a new '-l'. Leaves the qsub options alone when -AL is under 2, when they
749
- already request select/ncpus/mem (a -QS from the user wins), or when they
750
- are dynamic options starting with '!'.
745
+ -AW/-AH/-AS and -MV copy, cross copy or move files in -AL child processes
746
+ (DsArch.archive_in_passes()), as does -AQ with -XC; -CG, -DL of web files,
747
+ and -SW with -GX/-DX/-GI/-WT can run 'gatherxml'/'dcm'/'scm' in -AL
748
+ background processes. Every other action (such as -SS) runs one process.
749
+
750
+ Args:
751
+ acts -- bitmask of the action being run
752
+ """
753
+ if acts in (self.OPTS['AW'][0], self.OPTS['AH'][0], self.OPTS['AS'][0], self.OPTS['MV'][0]): return True
754
+ if acts & self.OPTS['CG'][0]: return True
755
+ if acts == self.OPTS['AQ'][0]: return 'XC' in self.params
756
+ if acts == self.OPTS['DL'][0]: return 'WF' in self.params and not ('HF' in self.params or 'SF' in self.params)
757
+ if acts == self.OPTS['SW'][0]:
758
+ return 'WF' in self.params and any(opt in self.params for opt in ('GX', 'DX', 'GI', 'WT'))
759
+ return False
760
+
761
+ def set_qsub_resources(self, acts):
762
+ """
763
+ Request PBS cpus and memory for the -AL processes of a -d batch job.
764
+
765
+ With -AL N (N > 1) the batch job runs up to N processes at once, so its PBS
766
+ chunk gets select=1:ncpus=N:mem=Ngb (1gb per process), added to the
767
+ existing '-l' list of the qsub options (after any walltime) or as a new
768
+ '-l'. Leaves the qsub options alone when -AL is under 2, when the action
769
+ cannot use -AL (see uses_async_limit()), when they already request
770
+ select/ncpus/mem (a -QS from the user wins), or when they are dynamic
771
+ options starting with '!'.
772
+
773
+ Args:
774
+ acts -- bitmask of the action being run
751
775
  """
752
776
  pcnt = self.params['AL'] if 'AL' in self.params else 0
753
- if not pcnt or pcnt < 2: return
777
+ if not pcnt or pcnt < 2 or not self.uses_async_limit(acts): return
754
778
  qopts = self.BOPTIONS['qoptions'] or ''
755
779
  if qopts.startswith('!') or re.search(r'(select|ncpus|mem)=', qopts): return
756
780
  res = "select=1:ncpus={0}:mem={0}gb".format(pcnt)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsarch
3
- Version: 3.0.18
3
+ Version: 3.0.19
4
4
  Summary: RDA Python package for utility program dsarch archive RDA data
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsarch