rda-python-dbms 3.0.1__py3-none-any.whl → 3.0.2__py3-none-any.whl

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.
@@ -13,11 +13,17 @@
13
13
  import sys
14
14
  import re
15
15
  import time
16
+ import glob
17
+ from os import path as op
16
18
  from rda_python_common.pg_cmd import PgCMD
17
19
  from rda_python_common.pg_split import PgSplit
18
20
 
19
21
  class PgChksum(PgCMD, PgSplit):
20
22
 
23
+ # Quasar backup status of a file, by bfile.type and bfile.status of its bid
24
+ QBSTATS = (('B', 'Quasar Backup'), ('D', 'Quasar Backup&DR'),
25
+ ('P', 'Quasar Backup Pending'), ('N', 'Not Backed Up'), ('C', 'In COLD_STORAGE'))
26
+
21
27
  def __init__(self):
22
28
  super().__init__() # initialize parent class
23
29
  self.PGSUM = {
@@ -48,6 +54,8 @@ class PgChksum(PgCMD, PgSplit):
48
54
  'end' : 0
49
55
  }
50
56
  self.MISSING = {} # dsid => list of missing file names with their location
57
+ self.BCKSTATS = {} # bid => Quasar backup status key in QBSTATS
58
+ self.COLDDIRS = {} # dsid => existing COLD_STORAGE directories of the dataset
51
59
  self.pgname = "pgchksum"
52
60
  self.pgcmd = None
53
61
 
@@ -88,7 +96,8 @@ class PgChksum(PgCMD, PgSplit):
88
96
  print(" -b - background process and no screen output")
89
97
  print(" -c - File count to re-evaluate checksum; for all gathered files if not specified")
90
98
  print(" -d - delayed mode background/batch processes on RDA machines/DAV PBS nodes")
91
- print(" -e - email the primary owner of each dataset a reminder listing its missing files")
99
+ print(" -e - email the primary owner of each dataset a reminder listing its missing files,")
100
+ print(" each with its Quasar backup status (W/S files)")
92
101
  print(" -f - Re-evaluate the specify File names only")
93
102
  print(" -i - Dataset IDs the files belong; for all files if not specified")
94
103
  print(" -k - skip the given file count to re-evaluate checksum")
@@ -147,7 +156,8 @@ class PgChksum(PgCMD, PgSplit):
147
156
  if self.PGSUM['x']: msg += 'and Fix '
148
157
  msg += "checksum/size/timestamp for {} {} files{}".format(fcnt, self.PVALS['TYPE'], self.PVALS['OFFSET'])
149
158
  self.pglog(msg, self.PVALS['emlsum'])
150
- cnts = {'cnt' : 0, 'pcnt' : 0, 'xcnt' : 0, 'ecnt' : 0, 'mcnt' : 0, 'ncnt' : 0, 'tcnt' : 0, 'scnt' : 0}
159
+ cnts = {'cnt' : 0, 'pcnt' : 0, 'xcnt' : 0, 'ecnt' : 0, 'mcnt' : 0, 'ncnt' : 0, 'tcnt' : 0, 'scnt' : 0,
160
+ 'bcnts' : {}} # Quasar backup status counts of the missing files
151
161
  if self.PGSUM['t'] == 'W':
152
162
  self.evaluate_webfile_checksum(fcnt, pgrecs, cnts)
153
163
  elif self.PGSUM['t'] == 'S':
@@ -204,23 +214,28 @@ class PgChksum(PgCMD, PgSplit):
204
214
  self.PVALS['TYPE'], dsid, self.pgname, fcnt, s, s, s, s)
205
215
  for i in range(fcnt):
206
216
  msg += "{}. {}\n".format(i+1, files[i])
217
+ if self.PGSUM['t'] != 'Q':
218
+ msg += ("\nA file on Quasar Backup can be restored with 'dsarch {} -RQ'; a file Not Backed Up " +
219
+ "cannot be restored from Quasar.\n").format(dsid)
207
220
  msg += "\n{} on {}\n".format(self.pgcmd, self.PGLOG['HOSTNAME'])
208
221
  receiver = ', '.join(o if o.find('@') > -1 else o + "@ucar.edu" for o in owners)
209
222
  if self.send_python_email(subject, receiver, msg, None, '', self.PVALS['emlerr']):
210
223
  self.pglog("{}: emailed {} about {} missing {} file{}".format(dsid, receiver, fcnt, self.PVALS['TYPE'], s), self.PVALS['emllog'])
211
224
 
212
225
  # record a missing file for the dataset owner email
213
- def add_missing_file(self, fname, pgrec, location):
226
+ def add_missing_file(self, fname, pgrec, location, status = None):
214
227
  if not self.PGSUM['e']: return
215
228
  dsid = pgrec['dsid']
216
229
  if dsid not in self.MISSING: self.MISSING[dsid] = []
217
- self.MISSING[dsid].append("{} (Type {}, {})".format(fname, pgrec['type'], location))
230
+ line = "{} (Type {}, missing {})".format(fname, pgrec['type'], location)
231
+ if status: line += "; " + status
232
+ self.MISSING[dsid].append(line)
218
233
 
219
234
  # get the checksum filelist for wfile
220
235
  def get_checksum_wfilelist(self):
221
236
  self.PVALS['TYPE'] = "Web"
222
237
  self.PVALS['BUCKET'] = 'gdex-data'
223
- flds = "wid, wfile, type, locflag, data_size, checksum, date_modified, time_modified"
238
+ flds = "wid, wfile, type, locflag, bid, data_size, checksum, date_modified, time_modified"
224
239
  if self.PGSUM['f']:
225
240
  pgrecs = self.pgmhget('wfile', 'wid, dsid', {'wfile' : self.PGSUM['f']})
226
241
  cnt = len(pgrecs['wid']) if pgrecs else 0
@@ -275,7 +290,7 @@ class PgChksum(PgCMD, PgSplit):
275
290
  def get_checksum_sfilelist(self):
276
291
  self.PVALS['TYPE'] = "Saved"
277
292
  self.PVALS['BUCKET'] = 'gdex-decsdata'
278
- flds = "sid, sfile, dsid, type, locflag, data_size, checksum, date_modified, time_modified"
293
+ flds = "sid, sfile, dsid, type, locflag, bid, data_size, checksum, date_modified, time_modified"
279
294
  hcnd = {}
280
295
  fcnt = self.PGSUM['c']
281
296
  cnt = len(self.PGSUM['f'])
@@ -367,6 +382,10 @@ class PgChksum(PgCMD, PgSplit):
367
382
  if cnts['ecnt'] > 0:
368
383
  cntmsg += "/{}".format(cnts['ecnt'])
369
384
  chkmsg += "/Nonexist"
385
+ if cnts['bcnts']:
386
+ # e.g. [Quasar Backup 10, Not Backed Up 2]
387
+ chkmsg += " [{}]".format(', '.join("{} {}".format(m, cnts['bcnts'][k])
388
+ for (k, m) in self.QBSTATS if k in cnts['bcnts']))
370
389
  self.pglog("{}{} {} files{} {}".format(cntmsg, ttlmsg, self.PVALS['TYPE'], self.PVALS['OFFSET'], chkmsg), logact)
371
390
 
372
391
  # evaluate checksums for web files
@@ -419,9 +438,7 @@ class PgChksum(PgCMD, PgSplit):
419
438
  if info:
420
439
  self.evaluate_file_status('DSK', fname, pgrec, info, cnts)
421
440
  else:
422
- self.pglog("{}: Type {} {} file is missing on Disk".format(fname, pgrec['type'], self.PVALS['TYPE']), self.PVALS['emllog'])
423
- self.add_missing_file(fname, pgrec, 'on Disk')
424
- cnts['ecnt'] += 1
441
+ self.report_missing_file(fname, pgrec, 'on Disk', cnts)
425
442
 
426
443
  # evaluate an object file
427
444
  def evaluate_object_file(self, fname, pgrec, bucket, cnts):
@@ -429,9 +446,7 @@ class PgChksum(PgCMD, PgSplit):
429
446
  if info:
430
447
  self.evaluate_file_status('OBS', "{}-{}".format(bucket, fname), pgrec, info, cnts)
431
448
  else:
432
- self.pglog("{}: Type {} {} file is missing in Object Store".format(fname, pgrec['type'], self.PVALS['TYPE']), self.PVALS['emllog'])
433
- self.add_missing_file("{}-{}".format(bucket, fname), pgrec, 'in Object Store')
434
- cnts['ecnt'] += 1
449
+ self.report_missing_file("{}-{}".format(bucket, fname), pgrec, 'in Object Store', cnts)
435
450
 
436
451
  # evaluate a quasar file
437
452
  def evaluate_quasar_file(self, fname, pgrec, endpoint, cnts):
@@ -439,10 +454,64 @@ class PgChksum(PgCMD, PgSplit):
439
454
  if info:
440
455
  self.evaluate_file_status('QSR', "{}-{}".format(endpoint, fname), pgrec, info, cnts)
441
456
  else:
442
- self.pglog("{}: Type {} {} file is missing on Quasar".format(fname, pgrec['type'], self.PVALS['TYPE']), self.PVALS['emllog'])
443
- self.add_missing_file("{}-{}".format(endpoint, fname), pgrec, 'on Quasar')
444
- cnts['ecnt'] += 1
457
+ self.report_missing_file("{}-{}".format(endpoint, fname), pgrec, 'on Quasar', cnts)
445
458
 
459
+ # log a missing file with the Quasar backup status of a Web/Saved file, so it
460
+ # tells whether the file can be restored; record it for the owner email
461
+ def report_missing_file(self, fname, pgrec, location, cnts):
462
+ msg = "{}: Type {} {} file is missing {}".format(fname, pgrec['type'], self.PVALS['TYPE'], location)
463
+ status = None
464
+ if self.PGSUM['t'] != 'Q':
465
+ bkey = self.quasar_backup_status(pgrec['bid'])
466
+ cnts['bcnts'][bkey] = cnts['bcnts'].get(bkey, 0) + 1
467
+ status = dict(self.QBSTATS)[bkey]
468
+ if bkey != 'N': status += " (Backup ID {})".format(pgrec['bid'])
469
+ if bkey in ('B', 'D'): status = "on " + status
470
+ if self.PGSUM['t'] == 'S' and location == 'on Disk':
471
+ sfile = pgrec['sfile']
472
+ # an absolute path under the dataset type directory has the same cold storage
473
+ # path as its relative one; other absolute paths are not in cold storage
474
+ tdir = "{}/{}/{}/".format(self.PGLOG['DECSHOME'], pgrec['dsid'], pgrec['type'])
475
+ if sfile.startswith(tdir): sfile = sfile[len(tdir):]
476
+ cfile = None if re.match(r'^/', sfile) else self.cold_storage_file(pgrec['dsid'], pgrec['type'], sfile)
477
+ if cfile:
478
+ cnts['bcnts']['C'] = cnts['bcnts'].get('C', 0) + 1
479
+ status += ", Found in COLD_STORAGE " + cfile
480
+ msg += "; " + status
481
+ self.pglog(msg, self.PVALS['emllog'])
482
+ self.add_missing_file(fname, pgrec, location, status)
483
+ cnts['ecnt'] += 1
484
+
485
+ # get the Quasar backup status key of a file by its bid: B-backup, D-backup&DR once
486
+ # the backup file status is A, P-pending otherwise, N-not backed up without a bid
487
+ def quasar_backup_status(self, bid):
488
+ if not bid: return 'N'
489
+ if bid not in self.BCKSTATS:
490
+ pgrec = self.pgget('bfile', 'type, status', "bid = {}".format(bid), self.PVALS['emlerr'])
491
+ if pgrec and pgrec['status'] == 'A' and pgrec['type'] in ('B', 'D'):
492
+ self.BCKSTATS[bid] = pgrec['type']
493
+ else:
494
+ self.BCKSTATS[bid] = 'P'
495
+ return self.BCKSTATS[bid]
496
+
497
+ # find a saved file moved into GLADE cold storage, under <DECSHOME>/COLD_STORAGE/<dsid>
498
+ # or <DECSHOME>/cold_storage_<YYYYMMDD>/COLD_STORAGE/<dsid>, the dated ones newest first;
499
+ # checking a migrated file only reads its inode, it does not recall it from tape
500
+ def cold_storage_file(self, dsid, stype, sfile):
501
+ if dsid not in self.COLDDIRS:
502
+ dhome = self.PGLOG['DECSHOME']
503
+ cdirs = []
504
+ for cname in ('COLD_STORAGE', 'COLD_STORAGE_SINGLE_COPY'):
505
+ cdirs.append("{}/{}/{}".format(dhome, cname, dsid))
506
+ for cdir in sorted(glob.glob(dhome + "/cold_storage_*"), reverse = True):
507
+ for cname in ('COLD_STORAGE', 'COLD_STORAGE_SINGLE_COPY'):
508
+ cdirs.append("{}/{}/{}".format(cdir, cname, dsid))
509
+ self.COLDDIRS[dsid] = [cdir for cdir in cdirs if op.isdir(cdir)]
510
+ for cdir in self.COLDDIRS[dsid]:
511
+ cfile = "{}/{}/{}".format(cdir, stype, sfile)
512
+ if op.exists(cfile): return cfile
513
+ return None
514
+
446
515
  # evaluate a file status
447
516
  def evaluate_file_status(self, lname, fname, pgrec, info, cnts):
448
517
  msg = ''
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dbms
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: RDA Python Package to manage PostreSQL databases
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dbms
@@ -33,9 +33,10 @@ current user:
33
33
  - `pgdbrestore` — restore a PostgreSQL database from a local dump directory
34
34
  - `pgchksum` — re-check archived files against their RDADB records
35
35
  (checksums, sizes and timestamps for Web and Saved files; sizes and
36
- timestamps only for Quasar backup files), optionally fix the records
37
- (`-x`), and email each dataset's primary owner a list of its missing
38
- files (`-e`)
36
+ timestamps only for Quasar backup files), report the Quasar backup status
37
+ (and COLD_STORAGE copy for Saved files) of each missing Web/Saved file,
38
+ optionally fix the records (`-x`), and email each dataset's primary owner
39
+ a list of its missing files (`-e`)
39
40
  - `pgcmptables` — compare the columns, indexes and data of identical tables
40
41
  on two database servers
41
42
  - `pgschema` — copy the tables of a schema into the same schema in another
@@ -10,7 +10,7 @@ rda_python_dbms/pg_ddllib.py,sha256=-zawllfUCe7QZNvza-u5TAYKP4yilC9K6TetHhoGenU,
10
10
  rda_python_dbms/pg_schema.py,sha256=Vsf5fKENYIOgMKE3yS0TkXhg-aeHZ2nYzpvltFAcdFY,5520
11
11
  rda_python_dbms/pg_seq.py,sha256=wNxqsyIjcTplJErPirZ-VkZ8HS4mgQUwPz8Xwd12kYA,4914
12
12
  rda_python_dbms/pgbackup.py,sha256=IuP3nQEEVR86YlSZuGyqsdz1-O9rgET76FnWB7iL3xk,4249
13
- rda_python_dbms/pgchksum.py,sha256=ePHdh2MQ3q8102Nm7znXPg8ToI-ohGnyg3f-ipIYWu8,24921
13
+ rda_python_dbms/pgchksum.py,sha256=1ayPqhOkD6GtXa5OXNgHgUSLy_09oowTjvl2DjIbptQ,28715
14
14
  rda_python_dbms/pgcmptables.py,sha256=LQ4KK4JuQs2z3nBdINWYgyiGu2MF9RZjS5_cAktbs6k,47123
15
15
  rda_python_dbms/pgdbdump.py,sha256=8d-1z16Y2DyJnQqex2wFu5svChKgncPnzdQ-iUftHg8,3831
16
16
  rda_python_dbms/pgdbrestore.py,sha256=d12hl1r6usK1oGIFAPVWUB_KkIH04PPlugxVSnLOX1g,3110
@@ -954,9 +954,9 @@ rda_python_dbms/tables/obsua/wind_5v2_0.json,sha256=NehMFE-GMNlD5Bh_fiacfSOmZWkr
954
954
  rda_python_dbms/tables/obsua/wind_60v2_0.json,sha256=jOyQgD5ph7YBMuQfvYz1Ln6X_-_Vdhl36HBexOHZ1dA,1067
955
955
  rda_python_dbms/tables/obsua/wind_8v2_0.json,sha256=5wtCThmFeOyiAGH_Qv3_jrXjhjo9b87ehlpI9QQY2SM,1064
956
956
  rda_python_dbms/tables/obsua/wind_9v2_0.json,sha256=7OKFUicOn15LL_WuF1VE_L8DjxsHmRP4uBnopuMIeTg,1064
957
- rda_python_dbms-3.0.1.dist-info/licenses/LICENSE,sha256=Uky6fiiVw7cakFVRtVpzg-mvqOQ-WcA_H8lHqhPRg58,1101
958
- rda_python_dbms-3.0.1.dist-info/METADATA,sha256=iqXoJ9_l_fP-i2prZLDDLZnXx9Cvko9VL2OCEXz1kAI,3293
959
- rda_python_dbms-3.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
960
- rda_python_dbms-3.0.1.dist-info/entry_points.txt,sha256=iti6iZS2OEeBqO5aHlWeMFcbuwN_2assidP0qeSkQ6E,346
961
- rda_python_dbms-3.0.1.dist-info/top_level.txt,sha256=4T1ShNUMyiqmwDIuDL7pDOm_S-6zWWW3Vy6OdAzYA4c,16
962
- rda_python_dbms-3.0.1.dist-info/RECORD,,
957
+ rda_python_dbms-3.0.2.dist-info/licenses/LICENSE,sha256=Uky6fiiVw7cakFVRtVpzg-mvqOQ-WcA_H8lHqhPRg58,1101
958
+ rda_python_dbms-3.0.2.dist-info/METADATA,sha256=na4-Kob7rwxFI7osOy_Yu6jfh_eKCRTudW5Ta4XmR6k,3399
959
+ rda_python_dbms-3.0.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
960
+ rda_python_dbms-3.0.2.dist-info/entry_points.txt,sha256=iti6iZS2OEeBqO5aHlWeMFcbuwN_2assidP0qeSkQ6E,346
961
+ rda_python_dbms-3.0.2.dist-info/top_level.txt,sha256=4T1ShNUMyiqmwDIuDL7pDOm_S-6zWWW3Vy6OdAzYA4c,16
962
+ rda_python_dbms-3.0.2.dist-info/RECORD,,