rda-python-dbms 3.0.0__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 = ''
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: rda_python_dbms
3
+ Version: 3.0.2
4
+ Summary: RDA Python Package to manage PostreSQL databases
5
+ Author-email: Zaihua Ji <zji@ucar.edu>
6
+ Project-URL: Homepage, https://github.com/NCAR/rda-python-dbms
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: rda_python_common
15
+ Dynamic: license-file
16
+
17
+ # rda_python_dbms
18
+
19
+ RDA Python package to manage RDA PostgreSQL databases.
20
+
21
+ ## Programs
22
+
23
+ The package installs the following command-line utilities, all running as the
24
+ current user:
25
+
26
+ - `pgddl` — create, drop or alter tables, indexes and keys from the table
27
+ definitions in `<tablename>.json` files
28
+ - `pgseq` — reset the sequences of all (or the given) tables in one or more
29
+ schemas
30
+ - `pgbackup` — dump the configured `rdadb` schemas on `rda-pgdb-02` into
31
+ `/data/pgbackup`, replacing existing backups and emailing a summary
32
+ - `pgdbdump` — dump a PostgreSQL database to a local directory
33
+ - `pgdbrestore` — restore a PostgreSQL database from a local dump directory
34
+ - `pgchksum` — re-check archived files against their RDADB records
35
+ (checksums, sizes and timestamps for Web and Saved files; sizes and
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`)
40
+ - `pgcmptables` — compare the columns, indexes and data of identical tables
41
+ on two database servers
42
+ - `pgschema` — copy the tables of a schema into the same schema in another
43
+ database on the same server
44
+
45
+ Run a program without its required options to print its usage. Do not use
46
+ `-h` for help: it is the host name option in several programs. `pgbackup`
47
+ has no usage text and starts the backup as soon as it runs.
48
+
49
+ ## Environment setup
50
+
51
+ Create a Python environment first; package installs in the next section run
52
+ inside whichever environment you activate here.
53
+
54
+ ### Option A — Python venv (DECS machines)
55
+
56
+ ```bash
57
+ python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
58
+ source $ENVHOME/bin/activate
59
+ ```
60
+
61
+ ### Option B — Conda (DAV/Casper)
62
+
63
+ ```bash
64
+ conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
65
+ conda activate $ENVHOME
66
+ ```
67
+
68
+ ## Installing rda-python-dbms
69
+
70
+ Pick whichever install mode fits your workflow. All variants pull in the
71
+ transitive dependency (`rda_python_common`) automatically.
72
+
73
+ For local development, clone this repo alongside your project and install it
74
+ in editable mode so that changes are picked up without re-installing:
75
+
76
+ ```bash
77
+ git clone https://github.com/NCAR/rda-python-dbms.git
78
+ cd rda-python-dbms
79
+ pip install -e .
80
+ ```
81
+
82
+ To test a specific branch (e.g. an in-progress feature or fix branch), pass
83
+ `-b/--branch` to `git clone`:
84
+
85
+ ```bash
86
+ git clone -b <branch-name> https://github.com/NCAR/rda-python-dbms.git
87
+ cd rda-python-dbms
88
+ pip install -e .
89
+ ```
90
+
91
+ For a regular (non-editable) install from a checkout:
92
+
93
+ ```bash
94
+ pip install /path/to/rda-python-dbms
95
+ ```
96
+
97
+ For a production install on a system that uses the published distribution:
98
+
99
+ ```bash
100
+ pip install rda_python_dbms
101
+ ```
@@ -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.0.dist-info/licenses/LICENSE,sha256=Uky6fiiVw7cakFVRtVpzg-mvqOQ-WcA_H8lHqhPRg58,1101
958
- rda_python_dbms-3.0.0.dist-info/METADATA,sha256=B9DrL7R_MmsAlJg1nRTq0jUBKjhPMFfITFGqlX5kqIE,617
959
- rda_python_dbms-3.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
960
- rda_python_dbms-3.0.0.dist-info/entry_points.txt,sha256=iti6iZS2OEeBqO5aHlWeMFcbuwN_2assidP0qeSkQ6E,346
961
- rda_python_dbms-3.0.0.dist-info/top_level.txt,sha256=4T1ShNUMyiqmwDIuDL7pDOm_S-6zWWW3Vy6OdAzYA4c,16
962
- rda_python_dbms-3.0.0.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,,
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rda_python_dbms
3
- Version: 3.0.0
4
- Summary: RDA Python Package to manage PostreSQL databases
5
- Author-email: Zaihua Ji <zji@ucar.edu>
6
- Project-URL: Homepage, https://github.com/NCAR/rda-python-dbms
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: rda_python_common
15
- Dynamic: license-file
16
-
17
- RDA python Package to manage RDA PostgreSQL databases.