rda-python-common 1.0.0__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.

Potentially problematic release.


This version of rda-python-common might be problematic. Click here for more details.

@@ -0,0 +1,3013 @@
1
+ #
2
+ ###############################################################################
3
+ #
4
+ # Title : PgFile.py
5
+ # Author : Zaihua Ji, zji@ucar.edu
6
+ # Date : 08/05/2020
7
+ # Purpose : python library module to copy, move and delete data files locally
8
+ # and remotely
9
+ #
10
+ # Work File : $DSSHOME/lib/python/PgFile.py
11
+ # Github : https://github.com/NCAR/rda-shared-libraries.git
12
+ #
13
+ ###############################################################################
14
+ #
15
+ import sys
16
+ import os
17
+ from os import path as op
18
+ import pwd
19
+ import grp
20
+ import stat
21
+ import re
22
+ import time
23
+ import glob
24
+ import json
25
+ import PgLOG
26
+ import PgUtil
27
+ import PgSIG
28
+ import PgDBI
29
+
30
+ CMDBTH = (0x0033) # return both stdout and stderr, 16 + 32 + 2 + 1
31
+ RETBTH = (0x0030) # return both stdout and stderr, 16 + 32
32
+ CMDRET = (0x0110) # return stdout and save error, 16 + 256
33
+ CMDERR = (0x0101) # display command and save error, 1 + 256
34
+ CMDGLB = (0x0313) # return stdout and save error for globus, 1+2+16+256+512
35
+
36
+ PGCMPS = {
37
+ # extension Compress Uncompress ArchiveFormat
38
+ 'Z' : ['compress -f', 'uncompress -f', 'Z'],
39
+ 'zip' : ['zip', 'unzip', 'ZIP'],
40
+ 'gz' : ['gzip', 'gunzip', 'GZ'],
41
+ 'xz' : ['xz', 'unxz', 'XZ'],
42
+ 'bz2' : ['bzip2', 'bunzip2', 'BZ2']
43
+ }
44
+ CMPSTR = '|'.join(PGCMPS)
45
+
46
+ PGTARS = {
47
+ # extension Packing Unpacking ArchiveFormat
48
+ 'tar' : ['tar -cvf', 'tar -xvf', 'TAR'],
49
+ 'tar.Z' : ['tar -Zcvf', 'tar -xvf', 'TAR.Z'],
50
+ 'zip' : ['zip -v', 'unzip -v', 'ZIP'],
51
+ 'tgz' : ['tar -zcvf', 'tar -xvf', 'TGZ'],
52
+ 'tar.gz' : ['tar -zcvf', 'tar -xvf', 'TAR.GZ'],
53
+ 'txz' : ['tar -cvJf', 'tar -xvf', 'TXZ'],
54
+ 'tar.xz' : ['tar -cvJf', 'tar -xvf', 'TAR.XZ'],
55
+ 'tbz2' : ['tar -cvjf', 'tar -xvf', 'TBZ2'],
56
+ 'tar.bz2' : ['tar -cvjf', 'tar -xvf', 'TAR.BZ2']
57
+ }
58
+
59
+ TARSTR = '|'.join(PGTARS)
60
+ DELDIRS = {}
61
+ TASKIDS = {} # cache unfinished
62
+ MD5CMD = 'md5sum'
63
+ SHA512CMD = 'sha512sum'
64
+ LHOST = "localhost"
65
+ OHOST = PgLOG.PGLOG['OBJCTSTR']
66
+ BHOST = PgLOG.PGLOG['BACKUPNM']
67
+ DHOST = PgLOG.PGLOG['DRDATANM']
68
+ OBJCTCMD = "isd_s3_cli"
69
+ BACKCMD = "dsglobus"
70
+
71
+ HLIMIT = 0 # HTAR file count limit
72
+ BLIMIT = 2 # minimum back tar file size in DB
73
+ DIRLVLS = 0
74
+
75
+ # record how many errors happen for working with HPSS, local or remote machines
76
+ ECNTS = {'D' : 0, 'H' : 0, 'L' : 0, 'R' : 0, 'O' : 0, 'B' : 0}
77
+ # up limits for how many continuing errors allowed
78
+ ELMTS = {'D' : 20, 'H' : 20, 'L' : 20, 'R' : 20, 'O' : 10, 'B' : 10}
79
+
80
+ # down storage hostnames & paths
81
+ DHOSTS = {
82
+ 'G' : PgLOG.PGLOG['GPFSNAME'],
83
+ 'O' : OHOST,
84
+ 'B' : BHOST,
85
+ 'D' : DHOST
86
+ }
87
+
88
+ DPATHS = {
89
+ 'G' : PgLOG.PGLOG['DSSDATA'],
90
+ 'O' : PgLOG.PGLOG['OBJCTBKT'],
91
+ 'B' : '/' + PgLOG.PGLOG['DEFDSID'], # backup globus endpoint
92
+ 'D' : '/' + PgLOG.PGLOG['DEFDSID'] # disaster recovery globus endpoint
93
+ }
94
+
95
+ QSTATS = {
96
+ 'A' : 'ACTIVE',
97
+ 'I' : 'INACTIVE',
98
+ 'S' : 'SUCCEEDED',
99
+ 'F' : 'FAILED',
100
+ }
101
+
102
+ QPOINTS = {
103
+ 'L' : 'rda-glade',
104
+ 'B' : 'rda-quasar',
105
+ 'D' : 'rda-quasar-drdata'
106
+ }
107
+
108
+ QHOSTS = {
109
+ 'rda-glade' : LHOST,
110
+ 'rda-quasar' : BHOST,
111
+ 'rda-quasar-drdata' : DHOST
112
+ }
113
+
114
+ ENDPOINTS = {
115
+ 'rda-glade' : "NCAR RDA GLADE",
116
+ 'rda-quasar' : "NCAR RDA Quasar",
117
+ 'rda-quasar-drdata' : "NCAR RDA Quasar DRDATA"
118
+ }
119
+
120
+ #
121
+ # reset the up limit for a specified error type
122
+ #
123
+ def reset_error_limit(etype, lmt):
124
+
125
+ ELMTS[etype] = lmt
126
+
127
+ #
128
+ # wrapping PgLOG.pglog() to show error and no fatal exit at the first call for retry
129
+ #
130
+ def errlog(msg, etype, retry = 0, logact = 0):
131
+
132
+ bckgrnd = PgLOG.PGLOG['BCKGRND']
133
+ logact |= PgLOG.ERRLOG
134
+ if not retry:
135
+ if msg and not re.search(r'\n$', msg): msg += "\n"
136
+ msg += "[The same execution will be retried in {} Seconds]".format(PgSIG.PGSIG['ETIME'])
137
+ PgLOG.PGLOG['BCKGRND'] = 1
138
+ logact &= ~(PgLOG.EMEROL|PgLOG.EXITLG)
139
+ elif ELMTS[etype]:
140
+ ECNTS[etype] += 1
141
+ if ECNTS[etype] >= ELMTS[etype]:
142
+ logact |= PgLOG.EXITLG
143
+ ECNTS[etype] = 0
144
+
145
+ if PgLOG.PGLOG['DSCHECK'] and logact&PgLOG.EXITLG: PgDBI.record_dscheck_error(msg)
146
+ PgLOG.pglog(msg, logact)
147
+ PgLOG.PGLOG['BCKGRND'] = bckgrnd
148
+ if not retry: time.sleep(PgSIG.PGSIG['ETIME'])
149
+
150
+ return PgLOG.FAILURE
151
+
152
+ #
153
+ # Copy a file from one host (including local host) to an another host (including local host)
154
+ # excluding copy file from remote host to remote host copying in background is permitted
155
+ #
156
+ # tofile - target file name
157
+ # fromfile - source file name
158
+ # tohost - target host name, default to LHOST
159
+ # fromhost - original host name, default to LHOST
160
+ #
161
+ # Return 1 if successful 0 if failed with error message generated in PgLOG.pgsystem() cached in PgLOG.PGLOG['SYSERR']
162
+ #
163
+ def copy_rda_file(tofile, fromfile, tohost = LHOST, fromhost = LHOST, logact = 0):
164
+
165
+ thost = strip_host_name(tohost)
166
+ fhost = strip_host_name(fromhost)
167
+
168
+ if PgUtil.pgcmp(thost, fhost, 1) == 0:
169
+ if PgUtil.pgcmp(thost, LHOST, 1) == 0:
170
+ return local_copy_local(tofile, fromfile, logact)
171
+ elif PgUtil.pgcmp(fhost, LHOST, 1) == 0:
172
+ if PgUtil.pgcmp(thost, OHOST, 1) == 0:
173
+ return local_copy_object(tofile, fromfile, None, None, logact)
174
+ elif PgUtil.pgcmp(thost, BHOST, 1) == 0:
175
+ return local_copy_backup(tofile, fromfile, QPOINTS['B'], logact)
176
+ elif PgUtil.pgcmp(thost, DHOST, 1) == 0:
177
+ return local_copy_backup(tofile, fromfile, QPOINTS['D'], logact)
178
+ else:
179
+ return local_copy_remote(tofile, fromfile, tohost, logact)
180
+ elif PgUtil.pgcmp(thost, LHOST, 1) == 0:
181
+ if PgUtil.pgcmp(fhost, OHOST, 1) == 0:
182
+ return object_copy_local(tofile, fromfile, None, logact)
183
+ elif PgUtil.pgcmp(fhost, BHOST, 1) == 0:
184
+ return backup_copy_local(tofile, fromfile, QPOINTS['B'], logact)
185
+ elif PgUtil.pgcmp(fhost, DHOST, 1) == 0:
186
+ return backup_copy_local(tofile, fromfile, QPOINTS['D'], logact)
187
+ else:
188
+ return remote_copy_local(tofile, fromfile, fromhost)
189
+
190
+ return errlog("{}-{}->{}-{}: Cannot copy file".format(fhost, fromfile, thost, tofile), 'O', 1, PgLOG.LGEREX)
191
+
192
+ #
193
+ # Copy a file locally
194
+ #
195
+ # tofile - target file name
196
+ # fromfile - source file name
197
+ #
198
+ def local_copy_local(tofile, fromfile, logact = 0):
199
+
200
+ finfo = check_local_file(fromfile, 0, logact)
201
+ if not finfo:
202
+ if finfo != None: return PgLOG.FAILURE
203
+ return lmsg(fromfile, "{} to copy to {}".format(PgLOG.PGLOG['MISSFILE'], tofile), logact)
204
+
205
+ target = tofile
206
+ ms = re.match(r'^(.+)/$', tofile)
207
+ if ms:
208
+ dir = ms.group(1)
209
+ tofile += op.basename(fromfile)
210
+ else:
211
+ dir = get_local_dirname(tofile)
212
+
213
+ if not make_local_directory(dir, logact): return PgLOG.FAILURE
214
+
215
+ cmd = "cp -{} {} {}".format(('f' if finfo['isfile'] else "rf"), fromfile, target)
216
+ reset = loop = 0
217
+ while((loop-reset) < 2):
218
+ info = None
219
+ PgLOG.PGLOG['ERR2STD'] = ['are the same file']
220
+ ret = PgLOG.pgsystem(cmd, logact, CMDERR)
221
+ PgLOG.PGLOG['ERR2STD'] = []
222
+ if ret:
223
+ info = check_local_file(tofile, 143, logact) # 1+2+4+8+128
224
+ if info:
225
+ if not info['isfile']:
226
+ set_local_mode(tofile, 0, 0, info['mode'], info['logname'], logact)
227
+ return PgLOG.SUCCESS
228
+ elif info['data_size'] == finfo['data_size']:
229
+ set_local_mode(tofile, 1, 0, info['mode'], info['logname'], logact)
230
+ return PgLOG.SUCCESS
231
+ elif info != None:
232
+ break
233
+
234
+ if PgLOG.PGLOG['SYSERR']:
235
+ errmsg = PgLOG.PGLOG['SYSERR']
236
+ else:
237
+ errmsg = "Error of '{}': Miss target file {}".format(cmd, tofile)
238
+ errlog(errmsg, 'L', (loop - reset), logact)
239
+ if loop == 0: reset = reset_local_info(tofile, info, logact)
240
+ loop += 1
241
+
242
+ return PgLOG.FAILURE
243
+
244
+ #
245
+ # Copy a local file to a remote host
246
+ #
247
+ # tofile - target file name
248
+ # fromfile - source file name
249
+ # host - remote host name
250
+ #
251
+ def local_copy_remote(tofile, fromfile, host, logact = 0):
252
+
253
+ finfo = check_local_file(fromfile, 0, logact)
254
+ if not finfo:
255
+ if finfo != None: return PgLOG.FAILURE
256
+ return lmsg(fromfile, "{} to copy to {}-{}".format(PgLOG.PGLOG['MISSFILE'], host, tofile), logact)
257
+
258
+ target = tofile
259
+ ms = re.match(r'^(.+)/$', tofile)
260
+ if ms:
261
+ dir = ms.group(1)
262
+ tofile += op.basename(fromfile)
263
+ else:
264
+ dir = op.dirname(tofile)
265
+
266
+ if not make_remote_directory(dir, host, logact): return PgLOG.FAILURE
267
+
268
+ cmd = PgLOG.get_sync_command(host)
269
+ cmd += " {} {}".format(fromfile, target)
270
+ for loop in range(2):
271
+ if PgLOG.pgsystem(cmd, logact, CMDERR):
272
+ info = check_remote_file(tofile, host, 0, logact)
273
+ if info:
274
+ if not finfo['isfile']:
275
+ set_remote_mode(tofile, 0, host, PgLOG.PGLOG['EXECMODE'])
276
+ return PgLOG.SUCCESS
277
+ elif info['data_size'] == finfo['data_size']:
278
+ set_remote_mode(tofile, 1, host, PgLOG.PGLOG['FILEMODE'])
279
+ return PgLOG.SUCCESS
280
+ elif info != None:
281
+ break
282
+
283
+ errlog(PgLOG.PGLOG['SYSERR'], 'R', loop, logact)
284
+
285
+ return PgLOG.FAILURE
286
+
287
+ #
288
+ # Copy a local file to object store
289
+ #
290
+ # tofile - target file name
291
+ # fromfile - source file name
292
+ # bucket - bucket name on Object store
293
+ # meta - reference to metadata hash
294
+ #
295
+ def local_copy_object(tofile, fromfile, bucket = None, meta = None, logact = 0):
296
+
297
+ if not bucket: bucket = PgLOG.PGLOG['OBJCTBKT']
298
+ if meta is None: meta = {}
299
+ if 'user' not in meta: meta['user'] = PgLOG.PGLOG['CURUID']
300
+ if 'group' not in meta: meta['group'] = PgLOG.PGLOG['RDAGRP']
301
+ uinfo = json.dumps(meta)
302
+
303
+ finfo = check_local_file(fromfile, 0, logact)
304
+ if not finfo:
305
+ if finfo != None: return PgLOG.FAILURE
306
+ return lmsg(fromfile, "{} to copy to {}-{}".format(PgLOG.PGLOG['MISSFILE'], OHOST, tofile), logact)
307
+
308
+ if not logact&PgLOG.OVRIDE:
309
+ tinfo = check_object_file(tofile, bucket, 0, logact)
310
+ if tinfo and tinfo['data_size'] > 0:
311
+ return PgLOG.pglog("{}-{}-{}: file exists already".format(OHOST, bucket, tofile), logact)
312
+
313
+ cmd = "{} ul -lf {} -b {} -k {} -md '{}'".format(OBJCTCMD, fromfile, bucket, tofile, uinfo)
314
+ for loop in range(2):
315
+ buf = PgLOG.pgsystem(cmd, logact, CMDBTH)
316
+ tinfo = check_object_file(tofile, bucket, 0, logact)
317
+ if tinfo:
318
+ if tinfo['data_size'] == finfo['data_size']:
319
+ return PgLOG.SUCCESS
320
+ elif tinfo != None:
321
+ break
322
+
323
+ errlog("Error Execute: {}\n{}".format(cmd, buf), 'O', loop, logact)
324
+
325
+ return PgLOG.FAILURE
326
+
327
+ #
328
+ # Copy multiple files from a Globus endpoint to another
329
+ # tofiles - target file name list, echo name leading with /dsnnn.n/ on Quasar and
330
+ # leading with /data/ or /decsdata/ on local glade disk
331
+ # fromfiles - source file name list, the same format as the tofiles
332
+ # topoint - target endpoint name, 'rda-glade', 'rda-quasar' or 'rda-quasar-drdata'
333
+ # frompoint - source endpoint name, the same choices as the topoint
334
+ #
335
+ def quasar_multiple_trasnfer(tofiles, fromfiles, topoint, frompoint, logact = 0):
336
+
337
+ ret = PgLOG.FAILURE
338
+ qstr = '{"action":"transfer","label":"%s","verify_checksum":true,' % ENDPOINTS[topoint]
339
+ # qstr = '{"action":"transfer","label":"%s",' % ENDPOINTS[topoint]
340
+ qstr += '"source_endpoint":"%s","destination_endpoint":"%s","files":[\n' % (frompoint, topoint)
341
+ fcnt = len(fromfiles)
342
+ bstr = ''
343
+ for i in range(fcnt):
344
+ qstr += '%s{"source_file":"%s","destination_file":"%s"}' % (bstr, fromfiles[i], tofiles[i])
345
+ if i == 0: bstr = ',\n'
346
+ qstr += ']}'
347
+
348
+ task = submit_globus_task(BACKCMD, topoint, logact, qstr)
349
+ if task['stat'] == 'S':
350
+ ret = PgLOG.SUCCESS
351
+ elif task['stat'] == 'A':
352
+ TASKIDS["{}-{}".format(topoint, tofiles[0])] = task['id']
353
+ ret = PgLOG.FINISH
354
+
355
+ return ret
356
+
357
+ #
358
+ # Copy a file from a Globus endpoint to another
359
+
360
+ # tofile - target file name, leading with /dsnnn.n/ on Quasar and
361
+ # leading with /data/ or /decsdata/ on local glade disk
362
+ # fromfile - source file, the same format as the tofile
363
+ # topoint - target endpoint name, 'rda-glade', 'rda-quasar' or 'rda-quasar-drdata'
364
+ # frompoint - source endpoint name, the same choices as the topoint
365
+ #
366
+ def endpoint_copy_endpoint(tofile, fromfile, topoint, frompoint, logact = 0):
367
+
368
+ ret = PgLOG.FAILURE
369
+ finfo = check_globus_file(fromfile, frompoint, 0, logact)
370
+ if not finfo:
371
+ if finfo != None: return ret
372
+ return lmsg(fromfile, "{} to copy {} file to {}-{}".format(PgLOG.PGLOG['MISSFILE'], frompoint, topoint, tofile), logact)
373
+
374
+ if not logact&PgLOG.OVRIDE:
375
+ tinfo = check_globus_file(tofile, topoint, 0, logact)
376
+ if tinfo and tinfo['data_size'] > 0:
377
+ return PgLOG.pglog("{}-{}: file exists already".format(topoint, tofile), logact)
378
+
379
+ cmd = "{} -t -vc -se {} -de {} -sf {} -df {}".format(BACKCMD, frompoint, topoint, fromfile, tofile)
380
+ task = submit_globus_task(cmd, topoint, logact)
381
+ if task['stat'] == 'S':
382
+ ret = PgLOG.SUCCESS
383
+ elif task['stat'] == 'A':
384
+ TASKIDS["{}-{}".format(topoint, tofile)] = task['id']
385
+ ret = PgLOG.FINISH
386
+
387
+ return ret
388
+
389
+ #
390
+ # submit a globus task and return a task id
391
+ #
392
+ def submit_globus_task(cmd, endpoint, logact = 0, qstr = None):
393
+
394
+ task = {'id' : None, 'stat' : 'U'}
395
+ loop = reset = 0
396
+ while (loop-reset) < 2:
397
+ buf = PgLOG.pgsystem(cmd, logact, CMDGLB, qstr)
398
+ syserr = PgLOG.PGLOG['SYSERR']
399
+ if buf and buf.find('a task has been created') > -1:
400
+ ms = re.search(r'Task ID:\s+(\S+)', buf)
401
+ if ms:
402
+ task['id'] = ms.group(1)
403
+ lp = 0
404
+ while lp < 2:
405
+ task['stat'] = check_globus_status(task['id'], endpoint, logact)
406
+ if task['stat'] == 'S': break
407
+ time.sleep(PgSIG.PGSIG['ETIME'])
408
+ lp += 1
409
+ if task['stat'] == 'S' or task['stat'] == 'A': break
410
+ if task['stat'] == 'F' and not syserr: break
411
+
412
+ errmsg = "Error Execute: " + cmd
413
+ if qstr: errmsg += " with stdin:\n" + qstr
414
+ if syserr:
415
+ errmsg += "\n" + syserr
416
+ (hstat, msg) = host_down_status('', QHOSTS[endpoint], 1, logact)
417
+ if hstat: errmsg += "\n" + msg
418
+ errlog(errmsg, 'B', (loop - reset), logact)
419
+ if loop == 0 and syserr and syserr.find('This user has too many pending jobs') > -1: reset = 1
420
+ loop += 1
421
+
422
+ if task['stat'] == 'S' or task['stat'] == 'A': ECNTS['B'] = 0 # reset error count
423
+ return task
424
+
425
+ #
426
+ # check Globus transfer status for given taskid. Cancel the task
427
+ # if PgLOG.NOWAIT presents and Details is neither OK nor Queued
428
+ #
429
+ def check_globus_status(taskid, endpoint = None, logact = 0):
430
+
431
+ ret = 'U'
432
+ if not taskid: return ret
433
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
434
+ mp = r'Status:\s+({})'.format('|'.join(QSTATS.values()))
435
+ cmd = "{} -gt --task-id {}".format(BACKCMD, taskid)
436
+ astats = ['OK', 'Queued']
437
+
438
+ for loop in range(2):
439
+ buf = PgLOG.pgsystem(cmd, logact, CMDRET)
440
+ if buf:
441
+ ms = re.search(mp, buf)
442
+ if ms:
443
+ ret = ms.group(1)[0]
444
+ if ret == 'A':
445
+ ms = re.search(r'Details:\s+(\S+)', buf)
446
+ if ms:
447
+ detail = ms.group(1)
448
+ if detail not in astats:
449
+ if logact&PgLOG.NOWAIT:
450
+ errmsg = "{}: Cancel Task due to {}:\n{}".format(taskid, detail, buf)
451
+ errlog(errmsg, 'B', 1, logact)
452
+ ccmd = "{} -ct --task-id {}".format(BACKCMD, taskid)
453
+ PgLOG.pgsystem(ccmd, logact, 7)
454
+ else:
455
+ time.sleep(PgSIG.PGSIG['ETIME'])
456
+ continue
457
+ break
458
+
459
+ errmsg = "Error Execute: " + cmd
460
+ if PgLOG.PGLOG['SYSERR']:
461
+ errmsg = "\n" + PgLOG.PGLOG['SYSERR']
462
+ (hstat, msg) = host_down_status('', QHOSTS[endpoint], 1, logact)
463
+ if hstat: errmsg += "\n" + msg
464
+ errlog(errmsg, 'B', loop, logact)
465
+
466
+ if ret == 'S' or ret == 'A': ECNTS['B'] = 0 # reset error count
467
+ return ret
468
+
469
+ #
470
+ # return SUCCESS if Globus transfer is done; FAILURE otherwise
471
+ #
472
+ def check_globus_finished(tofile, topoint, logact = 0):
473
+
474
+ ret = PgLOG.SUCCESS
475
+ ckey = "{}-{}".format(topoint, tofile)
476
+ if ckey in TASKIDS:
477
+ taskid = TASKIDS[ckey]
478
+ else:
479
+ errlog(ckey + ": Miss Task ID to check Status", 'B', 1, logact)
480
+ return PgLOG.FAILURE
481
+
482
+ lp = 0
483
+ if logact&PgLOG.NOWAIT:
484
+ act = logact&(~PgLOG.NOWAIT)
485
+ lps = 2
486
+ else:
487
+ act = logact
488
+ lps = 0
489
+
490
+ while True:
491
+ stat = check_globus_status(taskid, topoint, act)
492
+ if stat == 'A':
493
+ if lps:
494
+ lp += 1
495
+ if lp > lps: act = logact
496
+ time.sleep(PgSIG.PGSIG['ETIME'])
497
+ else:
498
+ if stat == 'S':
499
+ del TASKIDS[ckey]
500
+ else:
501
+ status = QSTATS[stat] if stat in QSTATS else 'UNKNOWN'
502
+ errlog("{}: Status '{}' for Task {}".format(ckey, status, taskid), 'B', 1, logact)
503
+ ret = PgLOG.FAILURE
504
+ break
505
+
506
+ return ret
507
+
508
+ #
509
+ # Copy a local file to Quasar backup tape system
510
+ #
511
+ # tofile - target file name, leading with /dsnnn.n/
512
+ # fromfile - source file name, leading with /data/ or /decsdata/
513
+ # endpoint - endpoint name on Quasar Backup Server
514
+ #
515
+ def local_copy_backup(tofile, fromfile, endpoint = None, logact = 0):
516
+
517
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
518
+ return endpoint_copy_endpoint(tofile, fromfile, endpoint, 'rda-glade', logact)
519
+
520
+ #
521
+ # Copy a Quasar backup file to local Globus endpoint
522
+ #
523
+ # tofile - target file name, leading with /data/ or /decsdata/
524
+ # fromfile - source file name, leading with /dsnnn.n/
525
+ # endpoint - endpoint name on Quasar Backup Server
526
+ #
527
+ def backup_copy_local(tofile, fromfile, endpoint = None, logact = 0):
528
+
529
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
530
+ return endpoint_copy_endpoint(tofile, fromfile, 'rda-glade', endpoint, logact)
531
+
532
+ #
533
+ # Copy a remote file to local
534
+ #
535
+ # tofile - target file name
536
+ # fromfile - source file name
537
+ # host - remote host name
538
+ #
539
+ def remote_copy_local(tofile, fromfile, host, logact = 0):
540
+
541
+ cmd = PgLOG.get_sync_command(host)
542
+ finfo = check_remote_file(fromfile, host, 0, logact)
543
+ if not finfo:
544
+ if finfo != None: return PgLOG.FAILURE
545
+ return errlog("{}-{}: {} to copy to {}".format(host, fromfile, PgLOG.PGLOG['MISSFILE'], tofile), 'R', 1, logact)
546
+
547
+ target = tofile
548
+ ms = re.match(r'^(.+)/$', tofile)
549
+ if ms:
550
+ dir = ms.group(1)
551
+ tofile += op.basename(fromfile)
552
+ else:
553
+ dir = get_local_dirname(tofile)
554
+
555
+ if not make_local_directory(dir, logact): return PgLOG.FAILURE
556
+
557
+ cmd += " -g {} {}".format(fromfile, target)
558
+ loop = reset = 0
559
+ while (loop-reset) < 2:
560
+ if PgLOG.pgsystem(cmd, logact, CMDERR):
561
+ info = check_local_file(tofile, 143, logact) # 1+2+4+8+128
562
+ if info:
563
+ if not info['isfile']:
564
+ set_local_mode(tofile, 0, PgLOG.PGLOG['EXECMODE'])
565
+ return PgLOG.SUCCESS
566
+ elif info['data_size'] == finfo['data_size']:
567
+ set_local_mode(tofile, 1, PgLOG.PGLOG['FILEMODE'])
568
+ return PgLOG.SUCCESS
569
+ elif info != None:
570
+ break
571
+
572
+ errlog(PgLOG.PGLOG['SYSERR'], 'L', (loop - reset), logact)
573
+ if loop == 0: reset = reset_local_info(tofile, info, logact)
574
+ loop += 1
575
+
576
+ return PgLOG.FAILURE
577
+
578
+ #
579
+ # Copy a object file to local
580
+ #
581
+ # tofile - target file name
582
+ # fromfile - source file name
583
+ # bucket - bucket name on Object store
584
+ #
585
+ def object_copy_local(tofile, fromfile, bucket = None, logact = 0):
586
+
587
+ ret = PgLOG.FAILURE
588
+ if not bucket: bucket = PgLOG.PGLOG['OBJCTBKT']
589
+ finfo = check_object_file(fromfile, bucket, 0, logact)
590
+ if not finfo:
591
+ if finfo != None: return ret
592
+ return lmsg(fromfile, "{}-{} to copy to {}".format(OHOST, PgLOG.PGLOG['MISSFILE'], tofile), logact)
593
+
594
+ cmd = "{} go -k {} -b {}".format(OBJCTCMD, fromfile, bucket)
595
+ fromname = op.basename(fromfile)
596
+ toname = op.basename(tofile)
597
+ if toname == tofile:
598
+ dir = odir = None
599
+ else:
600
+ dir = op.dirname(tofile)
601
+ odir = change_local_directory(dir, logact)
602
+ loop = reset = 0
603
+ while (loop-reset) < 2:
604
+ buf = PgLOG.pgsystem(cmd, logact, CMDBTH)
605
+ info = check_local_file(fromname, 143, logact) # 1+2+4+8+128
606
+ if info:
607
+ if info['data_size'] == finfo['data_size']:
608
+ set_local_mode(fromfile, info['isfile'], 0, info['mode'], info['logname'], logact)
609
+ if toname == fromname or move_local_file(toname, fromname, logact):
610
+ ret = PgLOG.SUCCESS
611
+ break
612
+
613
+
614
+ elif info != None:
615
+ break
616
+
617
+ errlog("Error Execute: {}\n{}".format(cmd, buf), 'L', (loop - reset), logact)
618
+ if loop == 0: reset = reset_local_info(tofile, info, logact)
619
+ loop += 1
620
+ if odir and odir != dir:
621
+ change_local_directory(odir, logact)
622
+
623
+ return ret
624
+
625
+ #
626
+ # Copy a remote file to object
627
+ #
628
+ # tofile - target object file name
629
+ # fromfile - source remote file name
630
+ # host - remote host name
631
+ # bucket - bucket name on Object store
632
+ # meta - reference to metadata hash
633
+ #
634
+ def remote_copy_object(tofile, fromfile, host, bucket = None, meta = None, logact = 0):
635
+
636
+ if is_local_host(host): return local_copy_object(tofile, fromfile, bucket, meta, logact)
637
+
638
+ locfile = "{}/{}".format(PgLOG.PGLOG['TMPPATH'], op.basename(tofile))
639
+ ret = remote_copy_local(locfile, fromfile, host, logact)
640
+ if ret:
641
+ ret = local_copy_object(tofile, locfile, bucket, meta, logact)
642
+ delete_local_file(locfile, logact)
643
+
644
+ return ret
645
+
646
+ #
647
+ # Copy an object file to remote
648
+ #
649
+ # tofile - target remote file name
650
+ # fromfile - source object file name
651
+ # host - remote host name
652
+ # bucket - bucket name on Object store
653
+ # meta - reference to metadata hash
654
+ #
655
+ def object_copy_remote(tofile, fromfile, host, bucket = None, logact = 0):
656
+
657
+ if is_local_host(host): return object_copy_local(tofile, fromfile, bucket, logact)
658
+
659
+ locfile = "{}/{}".format(PgLOG.PGLOG['TMPPATH'], op.basename(tofile))
660
+ ret = object_copy_local(locfile, fromfile, bucket, logact)
661
+ if ret:
662
+ ret = local_copy_remote(fromfile, locfile, host, logact)
663
+ delete_local_file(locfile, logact)
664
+
665
+ return ret
666
+
667
+ #
668
+ # Delete a file/directory on a given host name (including local host) no background process for deleting
669
+ #
670
+ # file - file name to be deleted
671
+ # host - host name the file on, default to LHOST
672
+ #
673
+ # Return 1 if successful 0 if failed with error message generated in PgLOG.pgsystem() cached in PgLOG.PGLOG['SYSERR']
674
+ #
675
+ def delete_rda_file(file, host, logact = 0):
676
+
677
+ shost = strip_host_name(host)
678
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
679
+ return delete_local_file(file, logact)
680
+ elif PgUtil.pgcmp(shost, OHOST, 1) == 0:
681
+ return delete_object_file(file, None, logact)
682
+ else:
683
+ return delete_remote_file(file, host, logact)
684
+
685
+ #
686
+ # Delete a local file/irectory
687
+ #
688
+ def delete_local_file(file, logact = 0):
689
+
690
+ info = check_local_file(file, 0, logact)
691
+ if not info: return PgLOG.FAILURE
692
+ cmd = "rm -rf "
693
+ cmd += file
694
+ loop = reset = 0
695
+ while (loop-reset) < 2:
696
+ if PgLOG.pgsystem(cmd, logact, CMDERR):
697
+ info = check_local_file(file, 14, logact)
698
+ if info is None:
699
+ if DIRLVLS: record_delete_directory(op.dirname(file), LHOST)
700
+ return PgLOG.SUCCESS
701
+ elif not info:
702
+ break # error checking file
703
+
704
+ errlog(PgLOG.PGLOG['SYSERR'], 'L', (loop - reset), logact)
705
+ if loop == 0: reset = reset_local_info(file, info, logact)
706
+ loop += 1
707
+
708
+ return PgLOG.FAILURE
709
+
710
+ #
711
+ # Delete file/directory on a remote host
712
+ #
713
+ def delete_remote_file(file, host, logact = 0):
714
+
715
+ if not check_remote_file(file, host, logact): return PgLOG.FAILURE
716
+
717
+ cmd = PgLOG.get_sync_command(host)
718
+
719
+ for loop in range(2):
720
+ if PgLOG.pgsystem("{} -d {}".format(cmd, file), logact, CMDERR):
721
+ if DIRLVLS: record_delete_directory(op.dirname(file), host)
722
+ return PgLOG.SUCCESS
723
+
724
+ errlog(PgLOG.PGLOG['SYSERR'], 'R', loop, logact)
725
+
726
+ return PgLOG.FAILURE
727
+
728
+ #
729
+ # Delete a file on object store
730
+ #
731
+ def delete_object_file(file, bucket = None, logact = 0):
732
+
733
+ if not bucket: bucket = PgLOG.PGLOG['OBJCTBKT']
734
+ for loop in range(2):
735
+ list = object_glob(file, bucket, 0, logact)
736
+ if not list: return PgLOG.FAILURE
737
+ errmsg = None
738
+ for key in list:
739
+ cmd = "{} dl {} -b {}".format(OBJCTCMD, key, bucket)
740
+ if not PgLOG.pgsystem(cmd, logact, CMDERR):
741
+ errmsg = PgLOG.PGLOG['SYSERR']
742
+ break
743
+
744
+ list = object_glob(file, bucket, 0, logact)
745
+ if not list: return PgLOG.SUCCESS
746
+ if errmsg: errlog(errmsg, 'O', loop, logact)
747
+
748
+ return PgLOG.FAILURE
749
+
750
+ #
751
+ # Delete a backup file on Quasar Server
752
+ #
753
+ def delete_backup_file(file, endpoint = None, logact = 0):
754
+
755
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
756
+ info = check_backup_file(file, endpoint, 0, logact)
757
+ if not info: return PgLOG.FAILURE
758
+
759
+ cmd = "{} -d -ep {} -tf {}".format(BACKCMD, endpoint, file)
760
+ task = submit_globus_task(cmd, endpoint, logact)
761
+ if task['stat'] == 'S':
762
+ return PgLOG.SUCCESS
763
+ elif task['stat'] == 'A':
764
+ TASKIDS["{}-{}".format(endpoint, file)] = task['id']
765
+ return PgLOG.FINISH
766
+
767
+ return PgLOG.FAILURE
768
+
769
+ #
770
+ # reset local file/directory information to make them writable for PgLOG.PGLOG['RDAUSER']
771
+ # file - file name (mandatory)
772
+ # info - gathered file info with option 14, None means file not exists
773
+ #
774
+ def reset_local_info(file, info = None, logact = 0):
775
+
776
+ ret = 0
777
+ if info:
778
+ if info['isfile']:
779
+ ret += reset_local_file(file, info, logact)
780
+ dir = get_local_dirname(file)
781
+ info = check_local_file(dir, 14, logact)
782
+ else:
783
+ dir = file
784
+ else:
785
+ dir = get_local_dirname(file)
786
+ info = check_local_file(dir, 14, logact)
787
+
788
+ if info: ret += reset_local_directory(dir, info, logact)
789
+
790
+ return 1 if ret else 0
791
+
792
+ #
793
+ # reset local directory group/mode
794
+ #
795
+ def reset_local_directory(dir, info = None, logact = 0):
796
+
797
+ ret = 0
798
+ if not (info and 'mode' in info and 'group' in info and 'logname' in info):
799
+ info = check_local_file(dir, 14, logact)
800
+ if info:
801
+ if info['mode'] and info['mode'] != 0o775:
802
+ ret += set_local_mode(dir, 0, 0o775, info['mode'], info['logname'], logact)
803
+ if info['group'] and PgLOG.PGLOG['RDAGRP'] != info['group']:
804
+ ret += change_local_group(dir, PgLOG.PGLOG['RDAGRP'], info['group'], info['logname'], logact)
805
+
806
+ return 1 if ret else 0
807
+
808
+ #
809
+ # reset local file group/mode
810
+ #
811
+ def reset_local_file(file, info = None, logact = 0):
812
+
813
+ ret = 0
814
+ if not (info and 'mode' in info and 'group' in info and 'logname' in info):
815
+ info = check_local_file(file, 14, logact)
816
+ if info:
817
+ if info['mode'] != 0o664:
818
+ ret += set_local_mode(file, 1, 0o664, info['mode'], info['logname'], logact)
819
+ if PgLOG.PGLOG['RDAGRP'] != info['group']:
820
+ ret += change_local_group(file, PgLOG.PGLOG['RDAGRP'], info['group'], info['logname'], logact)
821
+
822
+ return ret
823
+
824
+ #
825
+ # Move file locally or remotely on the same host no background process for moving
826
+ #
827
+ # tofile - target file name
828
+ # fromfile - original file name
829
+ # host - host name the file is moved on, default to LHOST
830
+ #
831
+ # Return PgLOG.SUCCESS if successful PgLOG.FAILURE otherwise
832
+ #
833
+ def move_rda_file(tofile, fromfile, host, logact = 0):
834
+
835
+ shost = strip_host_name(host)
836
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
837
+ return move_local_file(tofile, fromfile, logact)
838
+ elif PgUtil.pgcmp(shost, OHOST, 1) == 0:
839
+ return move_object_file(tofile, fromfile, None, None, logact)
840
+ else:
841
+ return move_remote_file(tofile, fromfile, host, logact)
842
+
843
+ #
844
+ # Move a file locally
845
+ #
846
+ # tofile - target file name
847
+ # fromfile - source file name
848
+ #
849
+ def move_local_file(tofile, fromfile, logact = 0):
850
+
851
+ dir = get_local_dirname(tofile)
852
+ info = check_local_file(fromfile, 0, logact)
853
+ tinfo = check_local_file(tofile, 0, logact)
854
+ if not info:
855
+ if info != None: return PgLOG.FAILURE
856
+ if tinfo:
857
+ PgLOG.pglog("{}: Moved to {} already".format(fromfile, tofile), PgLOG.LOGWRN)
858
+ return PgLOG.SUCCESS
859
+ else:
860
+ return errlog("{}: {} to move".format(fromfile, PgLOG.PGLOG['MISSFILE']), 'L', 1, logact)
861
+ if tinfo:
862
+ if tinfo['data_size'] > 0 and not logact&PgLOG.OVRIDE:
863
+ return errlog("{}: File exists, cannot move {} to it".format(tofile, fromfile), 'L', 1, logact)
864
+ elif tinfo != None:
865
+ return PgLOG.FAILURE
866
+
867
+ if not make_local_directory(dir, logact): return PgLOG.FAILURE
868
+
869
+ cmd = "mv {} {}".format(fromfile, tofile)
870
+ loop = reset = 0
871
+ while (loop-reset) < 2:
872
+ if PgLOG.pgsystem(cmd, logact, CMDERR):
873
+ if DIRLVLS: record_delete_directory(op.dirname(fromfile), LHOST)
874
+ return PgLOG.SUCCESS
875
+
876
+ errlog(PgLOG.PGLOG['SYSERR'], 'L', (loop - reset), logact)
877
+ if loop == 0: reset = reset_local_info(tofile, info, logact)
878
+ loop += 1
879
+
880
+ return PgLOG.FAILURE
881
+
882
+ #
883
+ # Move a remote file on the same host
884
+ #
885
+ # tofile - target file name
886
+ # fromfile - original file name
887
+ # host - remote host name
888
+ # locfile - local copy of tofile
889
+ #
890
+ def move_remote_file(tofile, fromfile, host, logact = 0):
891
+
892
+ if is_local_host(host): return move_local_file(tofile, fromfile, logact)
893
+
894
+ ret = PgLOG.FAILURE
895
+ dir = op.dirname(tofile)
896
+ info = check_remote_file(fromfile, host, 0, logact)
897
+ tinfo = check_remote_file(tofile, host, 0, logact)
898
+ if not info:
899
+ if info != None: return PgLOG.FAILURE
900
+ if tinfo:
901
+ PgLOG.pglog("{}-{}: Moved to {} already".format(host, fromfile, tofile), PgLOG.LOGWRN)
902
+ return PgLOG.SUCCESS
903
+ else:
904
+ return errlog("{}-{}: {} to move".format(host, fromfile, PgLOG.PGLOG['MISSFILE']), 'R', 1, logact)
905
+ if tinfo:
906
+ if tinfo['data_size'] > 0 and not logact&PgLOG.OVRIDE:
907
+ return errlog("{}-{}: File exists, cannot move {} to it".format(host, tofile, fromfile), 'R', 1, logact)
908
+ elif tinfo != None:
909
+ return PgLOG.FAILURE
910
+
911
+ if make_remote_directory(dir, host, logact):
912
+ locfile = "{}/{}".format(PgLOG.PGLOG['TMPPATH'], op.basename(tofile))
913
+ if remote_copy_local(locfile, fromfile, host, logact):
914
+ ret = local_copy_remote(tofile, locfile, host, logact)
915
+ delete_local_file(locfile, logact)
916
+ if ret:
917
+ ret = delete_remote_file(fromfile, host, logact)
918
+ if DIRLVLS: record_delete_directory(op.dirname(fromfile), host)
919
+
920
+ return ret
921
+
922
+ #
923
+ # Move an object file on Object Store
924
+ #
925
+ # tofile - target file name
926
+ # fromfile - original file name
927
+ # tobucket - target bucket name
928
+ # frombucket - original bucket name
929
+ #
930
+ def move_object_file(tofile, fromfile, tobucket, frombucket, logact = 0):
931
+
932
+ ret = PgLOG.FAILURE
933
+ if not tobucket: tobucket = PgLOG.PGLOG['OBJCTBKT']
934
+ if not frombucket: frombucket = tobucket
935
+ finfo = check_object_file(fromfile, frombucket, 0, logact)
936
+ tinfo = check_object_file(tofile, tobucket, 0, logact)
937
+ if not finfo:
938
+ if finfo != None: return PgLOG.FAILURE
939
+ if tinfo:
940
+ PgLOG.pglog("{}-{}: Moved to {}-{} already".format(frombucket, fromfile, tobucket, tofile), PgLOG.LOGWRN)
941
+ return PgLOG.SUCCESS
942
+ else:
943
+ return errlog("{}-{}: {} to move".format(frombucket, fromfile, PgLOG.PGLOG['MISSFILE']), 'R', 1, logact)
944
+ if tinfo:
945
+ if tinfo['data_size'] > 0 and not logact&PgLOG.OVRIDE:
946
+ return errlog("{}-{}: Object File exists, cannot move {}-{} to it".format(tobucket, tofile, frombucket, fromfile), 'R', 1, logact)
947
+ elif tinfo != None:
948
+ return PgLOG.FAILURE
949
+
950
+ cmd = "{} mv -b {} -db {} -k {} -dk {}".format(OBJCTCMD, frombucket, tobucket, fromfile, tofile)
951
+ ucmd = "{} gm -k {} -b {}".format(OBJCTCMD, fromfile, frombucket)
952
+ ubuf = PgLOG.pgsystem(ucmd, PgLOG.LOGWRN, CMDRET)
953
+ if ubuf and re.match(r'^\{', ubuf): cmd += " -md '{}'".format(ubuf)
954
+
955
+ for loop in range(2):
956
+ buf = PgLOG.pgsystem(cmd, logact, CMDBTH)
957
+ tinfo = check_object_file(tofile, tobucket, 0, logact)
958
+ if tinfo:
959
+ if tinfo['data_size'] == finfo['data_size']:
960
+ return PgLOG.SUCCESS
961
+ elif tinfo != None:
962
+ break
963
+
964
+ errlog("Error Execute: {}\n{}".format(cmd, buf), 'O', loop, logact)
965
+
966
+ return PgLOG.FAILURE
967
+
968
+ #
969
+ # Move an object path on Object Store and all the file keys under it
970
+ #
971
+ # topath - target path name
972
+ # frompath - original path name
973
+ # tobucket - target bucket name
974
+ # frombucket - original bucket name
975
+ #
976
+ def move_object_path(topath, frompath, tobucket, frombucket, logact = 0):
977
+
978
+ ret = PgLOG.FAILURE
979
+ if not tobucket: tobucket = PgLOG.PGLOG['OBJCTBKT']
980
+ if not frombucket: frombucket = tobucket
981
+ fcnt = check_object_path(frompath, frombucket, logact)
982
+ tcnt = check_object_path(topath, tobucket, logact)
983
+ if not fcnt:
984
+ if fcnt == None: return PgLOG.FAILURE
985
+ if tcnt:
986
+ PgLOG.pglog("{}-{}: Moved to {}-{} already".format(frombucket, frompath, tobucket, topath), PgLOG.LOGWRN)
987
+ return PgLOG.SUCCESS
988
+ else:
989
+ return errlog("{}-{}: {} to move".format(frombucket, frompath, PgLOG.PGLOG['MISSFILE']), 'R', 1, logact)
990
+
991
+ cmd = "{} mv -b {} -db {} -k {} -dk {}".format(OBJCTCMD, frombucket, tobucket, frompath, topath)
992
+
993
+ for loop in range(2):
994
+ buf = PgLOG.pgsystem(cmd, logact, CMDBTH)
995
+ fcnt = check_object_path(frompath, frombucket, logact)
996
+ if not fcnt: return PgLOG.SUCCESS
997
+ errlog("Error Execute: {}\n{}".format(cmd, buf), 'O', loop, logact)
998
+
999
+ return PgLOG.FAILURE
1000
+
1001
+ #
1002
+ # Move a backup file on Quasar Server
1003
+ #
1004
+ # tofile - target file name
1005
+ # fromfile - source file name
1006
+ # endpoint - Globus endpoint
1007
+ #
1008
+ def move_backup_file(tofile, fromfile, endpoint = None, logact = 0):
1009
+
1010
+ ret = PgLOG.FAILURE
1011
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
1012
+ finfo = check_backup_file(fromfile, endpoint, 0, logact)
1013
+ tinfo = check_backup_file(tofile, endpoint, 0, logact)
1014
+ if not finfo:
1015
+ if finfo != None: return ret
1016
+ if tinfo:
1017
+ PgLOG.pglog("{}: Moved to {} already".format(fromfile, tofile), PgLOG.LOGWRN)
1018
+ return PgLOG.SUCCESS
1019
+ else:
1020
+ return errlog("{}: {} to move".format(fromfile, PgLOG.PGLOG['MISSFILE']), 'B', 1, logact)
1021
+
1022
+ if tinfo:
1023
+ if tinfo['data_size'] > 0 and not logact&PgLOG.OVRIDE:
1024
+ return errlog("{}: File exists, cannot move {} to it".format(tofile, fromfile), 'B', 1, logact)
1025
+ elif tinfo != None:
1026
+ return ret
1027
+
1028
+ cmd = "{} --rename -ep {} --oldpath {} --newpath {}".format(BACKCMD, endpoint, fromfile, tofile)
1029
+ loop = 0
1030
+ while loop < 2:
1031
+ buf = PgLOG.pgsystem(cmd, logact, CMDRET)
1032
+ syserr = PgLOG.PGLOG['SYSERR']
1033
+ if buf:
1034
+ if buf.find('File or directory renamed successfully') > -1:
1035
+ ret = PgLOG.SUCCESS
1036
+ break
1037
+ if syserr:
1038
+ if syserr.find("No such file or directory") > -1:
1039
+ if make_backup_directory(op.dirname(tofile), endpoint, logact): continue
1040
+ errmsg = "Error Execute: {}\n{}".format(cmd, syserr)
1041
+ (hstat, msg) = host_down_status('', QHOSTS[endpoint], 1, logact)
1042
+ if hstat: errmsg += "\n" + msg
1043
+ errlog(errmsg, 'B', loop, logact)
1044
+ loop += 1
1045
+
1046
+ if ret == PgLOG.SUCCESS: ECNTS['B'] = 0 # reset error count
1047
+ return ret
1048
+
1049
+ #
1050
+ # Make a directory on a given host name (including local host)
1051
+ #
1052
+ # dir - directory path to be made
1053
+ # host - host name the directory on, default to LHOST
1054
+ #
1055
+ # Return PgLOG.SUCCESS(1) if successful or PgLOG.FAILURE(0) if failed
1056
+ #
1057
+ def make_rda_directory(dir, host, logact = 0):
1058
+
1059
+ if not dir: return PgLOG.SUCCESS
1060
+ shost = strip_host_name(host)
1061
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
1062
+ return make_local_directory(dir, logact)
1063
+ else:
1064
+ return make_remote_directory(dir, host, logact)
1065
+
1066
+ #
1067
+ # Make a local directory
1068
+ #
1069
+ # dir - directory path to be made
1070
+ #
1071
+ def make_local_directory(dir, logact = 0):
1072
+
1073
+ return make_one_local_directory(dir, None, logact)
1074
+
1075
+ #
1076
+ # Make a local directory recursively
1077
+ #
1078
+ def make_one_local_directory(dir, odir = None, logact = 0):
1079
+
1080
+ if not dir or op.isdir(dir): return PgLOG.SUCCESS
1081
+ if op.isfile(dir): return errlog(dir + ": is file, cannot make directory", 'L', 1, logact)
1082
+
1083
+ if not odir: odir = dir
1084
+ if is_root_directory(dir, 'L', LHOST, "make directory " + odir, logact): return PgLOG.FAILURE
1085
+ if not make_one_local_directory(op.dirname(dir), odir, logact): return PgLOG.FAILURE
1086
+
1087
+ loop = reset = 0
1088
+ while (loop-reset) < 2:
1089
+ try:
1090
+ os.mkdir(dir, PgLOG.PGLOG['EXECMODE'])
1091
+ except Exception as e:
1092
+ errmsg = str(e)
1093
+ if errmsg.find('File exists') > -1: return PgLOG.SUCCESS
1094
+ errlog(errmsg, 'L', (loop - reset), logact)
1095
+ if loop == 0: reset = reset_local_info(dir, None, logact)
1096
+ loop += 1
1097
+ else:
1098
+ return PgLOG.SUCCESS
1099
+
1100
+ return PgLOG.FAILURE
1101
+
1102
+ #
1103
+ # Make a directory on a remote host name
1104
+ #
1105
+ # dir - directory path to be made
1106
+ # host - host name the directory on
1107
+ #
1108
+ def make_remote_directory(dir, host, logact = 0):
1109
+
1110
+ return make_one_remote_directory(dir, None, host, logact)
1111
+
1112
+ def make_one_remote_directory(dir, odir, host, logact = 0):
1113
+
1114
+ info = check_remote_file(dir, host, 0, logact)
1115
+ if info:
1116
+ if info['isfile']: return errlog("{}-{}: is file, cannot make directory".format(host, dir), 'R', 1, logact)
1117
+ return PgLOG.SUCCESS
1118
+ elif info != None:
1119
+ return PgLOG.FAILURE
1120
+
1121
+ if not odir: odir = dir
1122
+ if is_root_directory(dir, 'R', host, "make directory {} on {}".format(odir, host), logact): return PgLOG.FAILURE
1123
+
1124
+ if make_one_remote_directory(op.dirname(dir), odir, host, logact):
1125
+ if PgLOG.pgsystem("{} {} {}".format(PgLOG.get_sync_command(host), PgLOG.PGLOG['TMPSYNC'], dir), logact, 5):
1126
+ set_remote_mode(dir, 0, host, PgLOG.PGLOG['EXECMODE'])
1127
+ return PgLOG.SUCCESS
1128
+
1129
+ return PgLOG.FAILURE
1130
+
1131
+ #
1132
+ # Make a quasar directory
1133
+ #
1134
+ # dir - directory path to be made
1135
+ #
1136
+ def make_backup_directory(dir, endpoint, logact = 0):
1137
+
1138
+ return make_one_backup_directory(dir, None, endpoint, logact)
1139
+
1140
+ #
1141
+ # Make a quasar directory recursively
1142
+ #
1143
+ def make_one_backup_directory(dir, odir, endpoint = None, logact = 0):
1144
+
1145
+ if not dir or dir == '/': return PgLOG.SUCCESS
1146
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
1147
+ info = check_backup_file(dir, endpoint, 0, logact)
1148
+ if info:
1149
+ if info['isfile']: return errlog("{}-{}: is file, cannot make backup directory".format(endpoint, dir), 'B', 1, logact)
1150
+ return PgLOG.SUCCESS
1151
+ elif info != None:
1152
+ return PgLOG.FAILURE
1153
+
1154
+ if not odir: odir = dir
1155
+ if not make_one_backup_directory(op.dirname(dir), odir, endpoint, logact): return PgLOG.FAILURE
1156
+
1157
+ cmd = "{} --mkdir -ep {} -p {}".format(BACKCMD, endpoint, dir)
1158
+ for loop in range(2):
1159
+ buf = PgLOG.pgsystem(cmd, logact, CMDRET)
1160
+ syserr = PgLOG.PGLOG['SYSERR']
1161
+ if buf:
1162
+ if(buf.find('The directory was created successfully') > -1 or
1163
+ buf.find("Path '{}' already exists".format(dir)) > -1):
1164
+ ret = PgLOG.SUCCESS
1165
+ break
1166
+ if syserr:
1167
+ if syserr.find("No such file or directory") > -1:
1168
+ ret = make_one_backup_directory(op.dirname(dir), odir, endpoint, logact)
1169
+ if ret == PgLOG.SUCCESS or loop: break
1170
+ time.sleep(PgSIG.PGSIG['ETIME'])
1171
+ else:
1172
+ errmsg = "Error Execute: {}\n{}".format(cmd, syserr)
1173
+ (hstat, msg) = host_down_status('', QHOSTS[endpoint], 1, logact)
1174
+ if hstat: errmsg += "\n" + msg
1175
+ errlog(errmsg, 'B', loop, logact)
1176
+
1177
+ if ret == PgLOG.SUCCESS: ECNTS['B'] = 0 # reset error count
1178
+ return ret
1179
+
1180
+ #
1181
+ # check and return 1 if a root directory
1182
+ #
1183
+ def is_root_directory(dir, etype, host = None, action = None, logact = 0):
1184
+
1185
+ ret = cnt = 0
1186
+
1187
+ if etype == 'H':
1188
+ ms = re.match(r'^({})(.*)$'.format(PgLOG.PGLOG['ALLROOTS']), dir)
1189
+ if ms:
1190
+ m2 = ms.group(2)
1191
+ if not m2 or m2 == '/': ret = 1
1192
+ else:
1193
+ cnt = 2
1194
+ elif re.match(r'^{}'.format(PgLOG.PGLOG['DSSDATA']), dir):
1195
+ ms = re.match(r'^({})(.*)$'.format(PgLOG.PGLOG['GPFSROOTS']), dir)
1196
+ if ms:
1197
+ m2 = ms.group(2)
1198
+ if not m2 or m2 == '/': ret = 1
1199
+ else:
1200
+ cnt = 4
1201
+ else:
1202
+ ms = re.match(r'^({})(.*)$'.format(PgLOG.PGLOG['HOMEROOTS']), dir)
1203
+ if ms:
1204
+ m2 = ms.group(2)
1205
+ if not m2 or m2 == '/': ret = 1
1206
+ else:
1207
+ cnt = 2
1208
+
1209
+ if cnt and re.match(r'^(/[^/]+){0,%d}(/*)$' % cnt, dir):
1210
+ ret = 1
1211
+
1212
+ if ret and action:
1213
+ cnt = 0
1214
+ errmsg = "{}: Cannot {} from {}".format(dir, action, PgLOG.PGLOG['HOSTNAME'])
1215
+ (hstat, msg) = host_down_status(dir, host, 0, logact)
1216
+ if hstat: errmsg += "\n" + msg
1217
+ errlog(errmsg, etype, 1, logact|PgLOG.ERRLOG)
1218
+
1219
+ return ret
1220
+
1221
+ #
1222
+ # set mode for a given direcory/file on a given host (include local host)
1223
+ #
1224
+ def set_rda_mode(file, isfile, host, nmode = None, omode = None, logname = None, logact = 0):
1225
+
1226
+ shost = strip_host_name(host)
1227
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
1228
+ return set_local_mode(file, isfile, nmode, omode, logname, logact)
1229
+ else:
1230
+ return set_remote_mode(file, isfile, host, nmode, omode, logact)
1231
+
1232
+ #
1233
+ # set mode for given local directory or file
1234
+ #
1235
+ def set_local_mode(file, isfile = 1, nmode = 0, omode = 0, logname = None, logact = 0):
1236
+
1237
+ if not nmode: nmode = (PgLOG.PGLOG['FILEMODE'] if isfile else PgLOG.PGLOG['EXECMODE'])
1238
+ if not (omode and logname):
1239
+ info = check_local_file(file, 6)
1240
+ if not info:
1241
+ if info != None: return PgLOG.FAILURE
1242
+ return lmsg(file, "{} to set mode({})".format(PgLOG.PGLOG['MISSFILE'], PgLOG.int2base(nmode, 8)), logact)
1243
+ omode = info['mode']
1244
+ logname = info['logname']
1245
+
1246
+ if nmode == omode: return PgLOG.SUCCESS
1247
+
1248
+ try:
1249
+ os.chmod(file, nmode)
1250
+ except Exception as e:
1251
+ return errlog(str(e), 'L', 1, logact)
1252
+
1253
+ return PgLOG.SUCCESS
1254
+
1255
+ #
1256
+ # set mode for given directory or file on remote host
1257
+ #
1258
+ def set_remote_mode(file, isfile, host, nmode = 0, omode = 0, logact = 0):
1259
+
1260
+ if not nmode: nmode = (PgLOG.PGLOG['FILEMODE'] if isfile else PgLOG.PGLOG['EXECMODE'])
1261
+ if not omode:
1262
+ info = check_remote_file(file, host, 6)
1263
+ if not info:
1264
+ if info != None: return PgLOG.FAILURE
1265
+ return errlog("{}-{}: {} to set mode({})".format(host, file, PgLOG.PGLOG['MISSFILE'], PgLOG.int2base(nmode, 8)), 'R', 1, logact)
1266
+ omode = info['mode']
1267
+
1268
+ if nmode == omode: return PgLOG.SUCCESS
1269
+ return PgLOG.pgsystem("{} -m {} {}".format(PgLOG.get_sync_command(host), PgLOG.int2base(nmode, 8), file), logact, 5)
1270
+
1271
+ #
1272
+ # change group for given local directory or file
1273
+ #
1274
+ def change_local_group(file, ngrp = None, ogrp = None, logname = None, logact = 0):
1275
+
1276
+ if not ngrp:
1277
+ ngid = PgLOG.PGLOG['RDAGID']
1278
+ else:
1279
+ ngid = grp.getgrnam[ngrp].gr_gid
1280
+ if logact and logact&PgLOG.EXITLG: logact &=~PgLOG.EXITLG
1281
+ if not (ogrp and logname):
1282
+ info = check_local_file(file, 10, logact)
1283
+ if not info:
1284
+ if info != None: return PgLOG.FAILURE
1285
+ return errlog("{}: {} to change group({})".format(file, PgLOG.PGLOG['MISSFILE'], ngrp), 'L', 1, logact)
1286
+ ogid = info['gid']
1287
+ ouid = info['uid']
1288
+ else:
1289
+ ouid = pwd.getpwnam(logname).pw_uid
1290
+ ogid = grp.getgrnam(logname).gr_gid
1291
+
1292
+ if ngid == ogid: return PgLOG.SUCCESS
1293
+
1294
+ try:
1295
+ os.chown(file, ouid, ngid)
1296
+ except Exception as e:
1297
+ return errlog(str(e), 'L', 1, logact)
1298
+
1299
+ #
1300
+ # Check if given path on a specified host or the host itself are down
1301
+ #
1302
+ # path: path name to be checked
1303
+ # host: host name the file on, default to LHOST
1304
+ # chkopt: 1 - do a file/path check, 0 - do not
1305
+ #
1306
+ # Return array of 2 (hstat, msg)
1307
+ # hstat: 0 if system is up and accessible,
1308
+ # 1 - host is down,
1309
+ # 2 - if path not accessible
1310
+ # negative values if planned system down
1311
+ # msg: None - stat == 0
1312
+ # an unempty string for system down message - stat != 0
1313
+ #
1314
+ def host_down_status(path, host, chkopt = 0, logact = 0):
1315
+
1316
+ shost = strip_host_name(host)
1317
+ hstat = 0
1318
+ rets = [0, None]
1319
+
1320
+ msg = hostname = None
1321
+
1322
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
1323
+ if not path or (chkopt and check_local_file(path)): return rets
1324
+ msg = path + ": is not accessible"
1325
+ flag = "L"
1326
+ if re.match(r'^(/{}/|{})'.format(PgLOG.PGLOG['GPFSNAME'], PgLOG.PGLOG['DSSDATA']), path):
1327
+ hstat = 1
1328
+ hostname = PgLOG.PGLOG['GPFSNAME']
1329
+ else:
1330
+ hstat = 2
1331
+
1332
+ elif PgUtil.pgcmp(shost, PgLOG.PGLOG['GPFSNAME'], 1) == 0:
1333
+ if not path or (chkopt and check_local_file(path)): return rets
1334
+ msg = path + ": is not accessible"
1335
+ flag = "L"
1336
+ hstat = 1
1337
+ hostname = PgLOG.PGLOG['GPFSNAME']
1338
+ elif PgUtil.pgcmp(shost, BHOST, 1) == 0:
1339
+ if path:
1340
+ hstat = 2
1341
+ else:
1342
+ hstat = 1
1343
+ path = DPATHS['B']
1344
+
1345
+ if chkopt and check_backup_file(path, QPOINTS['B']): return rets
1346
+ hostname = BHOST
1347
+ msg = "{}-{}: is not accessible".format(hostname, path)
1348
+ flag = "B"
1349
+ elif PgUtil.pgcmp(shost, DHOST, 1) == 0:
1350
+ if path:
1351
+ hstat = 2
1352
+ else:
1353
+ hstat = 1
1354
+ path = DPATHS['B']
1355
+
1356
+ if chkopt and check_backup_file(path, QPOINTS['D']): return rets
1357
+ hostname = DHOST
1358
+ msg = "{}-{}: is not accessible".format(hostname, path)
1359
+ flag = "D"
1360
+ elif PgUtil.pgcmp(shost, OHOST, 1) == 0:
1361
+ if path:
1362
+ hstat = 2
1363
+ else:
1364
+ hstat = 1
1365
+ path = PgLOG.PGLOG['OBJCTBKT']
1366
+
1367
+ if chkopt and check_object_file(path): return rets
1368
+
1369
+ hostname = OHOST
1370
+ msg = "{}-{}: is not accessible".format(hostname, path)
1371
+ flag = "O"
1372
+ elif PgUtil.pgcmp(shost, PgLOG.PGLOG['PGBATCH'], 1):
1373
+ if path and chkopt and check_remote_file(path, host): return rets
1374
+ estat = ping_remote_host(host)
1375
+ if estat:
1376
+ hstat = 1
1377
+ hostname = host
1378
+ else:
1379
+ if not path: return rets
1380
+ if re.match(r'^/{}/'.format(PgLOG.PGLOG['GPFSNAME']), path):
1381
+ hstat = 1
1382
+ hostname = PgLOG.PGLOG['GPFSNAME']
1383
+ else:
1384
+ hstat = 2
1385
+ hostname = host
1386
+
1387
+ flag = "R"
1388
+ msg = "{}-{}: is not accessible".format(host, path)
1389
+ elif PgLOG.get_host(1) == PgLOG.PGLOG['PGBATCH']: # local host is a batch node
1390
+ if not path or (chkopt and check_local_file(path)): return rets
1391
+ msg = path + ": is not accessible"
1392
+ flag = "L"
1393
+ if re.match(r'^(/{}/|{})'.format(PgLOG.PGLOG['GPFSNAME'], PgLOG.PGLOG['DSSDATA']), path):
1394
+ hstat = 1
1395
+ hostname = PgLOG.PGLOG['GPFSNAME']
1396
+ else:
1397
+ hstat = 2
1398
+
1399
+ msg += " at the moment Checked on " + PgLOG.PGLOG['HOSTNAME']
1400
+
1401
+ if hostname:
1402
+ estat = PgDBI.system_down_message(hostname, path, 0, logact)
1403
+ if estat:
1404
+ hstat = -hstat
1405
+ msg += "\n" + estat
1406
+
1407
+ if logact and (chkopt or hstat < 0): errlog(msg, flag, 1, logact)
1408
+
1409
+ return (hstat, msg)
1410
+
1411
+ #
1412
+ # Check if given path on a specified host is down or not
1413
+ #
1414
+ # path: path name to be checked
1415
+ # host: host name the file on, default to LHOST
1416
+ #
1417
+ # Return errmsg if not accessible and None otherwise
1418
+ #
1419
+ def check_host_down(path, host, logact = 0):
1420
+
1421
+ (hstat, msg) = host_down_status(path, host, 1, logact)
1422
+
1423
+ return msg if hstat else None
1424
+
1425
+ #
1426
+ # Check if given service name is accessible from a specified host
1427
+ #
1428
+ # sname: service name to be checked
1429
+ # fhost: from host name to connect to service, default to LHOST
1430
+ #
1431
+ # reset the service flag to A or I accordingly
1432
+ #
1433
+ # Return 0 if accessible, dsservice.sindex if not, and -1 if can not be checked
1434
+ #
1435
+ def check_service_accessibilty(sname, fhost = None, logact = 0):
1436
+
1437
+ if not fhost: fhost = PgLOG.PGLOG['HOSTNAME']
1438
+ pgrec = PgDBI.pgget("dsservice", "*", "service = '{}' AND hostname = '{}'".format(sname, fhost), logact)
1439
+ if not pgrec:
1440
+ PgLOG.pglog("dsservice: Access {} from {} is not defined in RDA Configuration".format(sname, fhost), logact)
1441
+ return -1
1442
+
1443
+ path = sname if (pgrec['flag'] == "H" or pgrec['flag'] == "G") else None
1444
+ (hstat, msg) = host_down_status(path, fhost, 1, logact)
1445
+
1446
+ return msg if hstat else None
1447
+
1448
+ #
1449
+ # check if this host is a local host for given host name
1450
+ #
1451
+ def is_local_host(host):
1452
+
1453
+ host = strip_host_name(host)
1454
+ if host == LHOST or PgLOG.valid_batch_host(host): return 1
1455
+
1456
+ return 0
1457
+
1458
+ #
1459
+ # check and return action string on a node other than local one
1460
+ #
1461
+ def local_host_action(host, action, info, logact = 0):
1462
+
1463
+ if is_local_host(host): return 1
1464
+ if not logact: return 0
1465
+
1466
+ if host == "partition":
1467
+ msg = "for individual partition"
1468
+ elif host == "rda_config":
1469
+ msg = "via https://rda.ucar.edu/internal/rda_pg_config"
1470
+ elif host in PgLOG.BCHCMDS:
1471
+ msg = "on a {} Node".format(host)
1472
+ else:
1473
+ msg = "on " + host
1474
+
1475
+ return PgLOG.pglog("{}: Cannot {}, try {}".format(info, action, msg), logact)
1476
+
1477
+ #
1478
+ # ping a given remote host name
1479
+ #
1480
+ # return None if system is up error messge if not
1481
+ #
1482
+ def ping_remote_host(host):
1483
+
1484
+ while True:
1485
+ buf = PgLOG.pgsystem("ping -c 3 " + host, PgLOG.LOGWRN, CMDRET)
1486
+ if buf:
1487
+ ms = re.search(r'3 packets transmitted, (\d)', buf)
1488
+ if ms:
1489
+ if int(ms.group(1)) > 0:
1490
+ return None
1491
+ else:
1492
+ return host + " seems down not accessible"
1493
+ if PgLOG.PGLOG['SYSERR']:
1494
+ if PgLOG.PGLOG['SYSERR'].find("ping: unknown host") > -1 and host.find('.') > -1:
1495
+ host += ".ucar.edu"
1496
+ continue
1497
+ return PgLOG.PGLOG['SYSERR']
1498
+ else:
1499
+ return "Cannot ping " + host
1500
+
1501
+ #
1502
+ # compare given two host names, return 1 if same and 0 otherwise
1503
+ #
1504
+ def same_hosts(host1, host2):
1505
+
1506
+ host1 = strip_host_name(host1)
1507
+ host2 = strip_host_name(host2)
1508
+
1509
+ return (1 if PgUtil.pgcmp(host1, host2, 1) == 0 else 0)
1510
+
1511
+ #
1512
+ # strip and identify the proper host name
1513
+ #
1514
+ def strip_host_name(host):
1515
+
1516
+ if not host: return LHOST
1517
+
1518
+ ms = re.match(r'^([^\.]+)\.', host)
1519
+ if ms: host = ms.group(1)
1520
+ if PgUtil.pgcmp(host, PgLOG.PGLOG['HOSTNAME'], 1) == 0:
1521
+ return LHOST
1522
+ else:
1523
+ return host
1524
+
1525
+ #
1526
+ # Check a file stuatus info on a given host name (including local host) no background process for checking
1527
+ #
1528
+ # file: file name to be checked
1529
+ # host: host name the file on, default to LHOST
1530
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1531
+ # 1 - get date/time modified (date_modified, time_modfied)
1532
+ # 2 - get file owner's login name (logname)
1533
+ # 4 - get permission mode in 3 octal digits (mode)
1534
+ # 8 - get group name (group)
1535
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1536
+ # 32 - get checksum (checksum), work for local file only
1537
+ #
1538
+ # Return a dict of file info, or None if file not exists
1539
+ #
1540
+ def check_rda_file(file, host = LHOST, opt = 0, logact = 0):
1541
+
1542
+ shost = strip_host_name(host)
1543
+
1544
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
1545
+ return check_local_file(file, opt, logact)
1546
+ elif PgUtil.pgcmp(shost, OHOST, 1) == 0:
1547
+ return check_object_file(file, None, opt, logact)
1548
+ elif PgUtil.pgcmp(shost, BHOST, 1) == 0:
1549
+ return check_backup_file(file, QPOINTS['B'], opt, logact)
1550
+ elif PgUtil.pgcmp(shost, DHOST, 1) == 0:
1551
+ return check_backup_file(file, QPOINTS['D'], opt, logact)
1552
+ else:
1553
+ return check_remote_file(file, host, opt, logact)
1554
+
1555
+ #
1556
+ # wrapper to check_local_file() and check_globus_file() to check info for a file
1557
+ # on local or remote Globus endpoints
1558
+ #
1559
+ def check_globus_file(file, endpoint = None, opt = 0, logact = 0):
1560
+
1561
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
1562
+ if endpoint == 'rda-glade':
1563
+ if re.match(r'^/(data|decsdata)/', file): file = PgLOG.PGLOG['DSSDATA'] + file
1564
+ return check_local_file(file, opt, logact)
1565
+ else:
1566
+ return check_backup_file(file, endpoint, opt, logact)
1567
+
1568
+ #
1569
+ # check and get local file status information
1570
+ #
1571
+ # file: local File name
1572
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1573
+ # 1 - get date/time modified (date_modified, time_modfied)
1574
+ # 2 - get file owner's login name (logname)
1575
+ # 4 - get permission mode in 3 octal digits (mode)
1576
+ # 8 - get group name (group)
1577
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1578
+ # 32 - get checksum (checksum)
1579
+ # 64 - remove file too small
1580
+ # 128 - check twice for missing file
1581
+ #
1582
+ # Return: a dict of file info, or None if not exists
1583
+ #
1584
+ def check_local_file(file, opt = 0, logact = 0):
1585
+
1586
+ ret = None
1587
+ if not file: return ret
1588
+ loop = 0
1589
+ while loop < 2:
1590
+ if op.exists(file):
1591
+ try:
1592
+ fstat = os.stat(file)
1593
+ ret = local_file_stat(file, fstat, opt, logact)
1594
+ break
1595
+ except Exception as e:
1596
+ errmsg = "{}: {}".format(file, str(e))
1597
+ (hstat, msg) = host_down_status(file, LHOST, 0, logact)
1598
+ if hstat: errmsg += "\n" + msg
1599
+ errlog(errmsg, 'L', loop, logact)
1600
+ else:
1601
+ if loop > 0 or opt&128 == 0: break
1602
+ PgLOG.pglog(file + ": check it again in a moment", PgLOG.LOGWRN)
1603
+ time.sleep(6)
1604
+ loop += 1
1605
+
1606
+ if loop > 1: return PgLOG.FAILURE
1607
+ ECNTS['L'] = 0 # reset error count
1608
+ return ret
1609
+
1610
+ #
1611
+ # local function to get local file stat
1612
+ #
1613
+ def local_file_stat(file, fstat, opt, logact):
1614
+
1615
+ if not fstat:
1616
+ errlog(file + ": Error check file stat", 'L', 1, logact)
1617
+ return None
1618
+
1619
+ info = {}
1620
+ info['isfile'] = (1 if stat.S_ISREG(fstat.st_mode) else 0)
1621
+ info['data_size'] = fstat.st_size
1622
+ info['fname'] = op.basename(file)
1623
+ if not opt: return info
1624
+ if opt&64 and info['isfile'] and info['data_size'] < PgLOG.PGLOG['MINSIZE']:
1625
+ PgLOG.pglog("{}: Remove {} file".format(file, ("Small({}B)".format(info['data_size']) if info['data_size'] else "Empty")), logact&~PgLOG.EXITLG)
1626
+ delete_local_file(file, logact)
1627
+ return None
1628
+
1629
+ if opt&17:
1630
+ mdate, mtime = PgUtil.get_date_time(fstat.st_mtime)
1631
+ if opt&1:
1632
+ info['date_modified'] = mdate
1633
+ info['time_modified'] = mtime
1634
+ cdate, ctime = PgUtil.get_date_time(fstat.st_ctime)
1635
+ info['date_created'] = cdate
1636
+ info['time_created'] = ctime
1637
+ if opt&16: info['week_day'] = PgUtil.get_weekday(mdate)
1638
+
1639
+ if opt&2:
1640
+ info['uid'] = fstat.st_uid
1641
+ info['logname'] = pwd.getpwuid(info['uid']).pw_name
1642
+ if opt&4: info['mode'] = stat.S_IMODE(fstat.st_mode)
1643
+ if opt&8:
1644
+ info['gid'] = fstat.st_gid
1645
+ info['group'] = grp.getgrgid(info['gid']).gr_name
1646
+ if opt&32: info['checksum'] = get_md5sum(file, 0, logact)
1647
+
1648
+ return info
1649
+
1650
+ #
1651
+ # check and get file status information of a file on remote host
1652
+ #
1653
+ # file: remote File name
1654
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1655
+ # 1 - get date/time modified (date_modified, time_modfied)
1656
+ # 2 - file owner's login name (logname), assumed 'rdadata'
1657
+ # 4 - get permission mode in 3 octal digits (mode)
1658
+ # 8 - get group name (group), assumed 'dss'
1659
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1660
+ #
1661
+ # Return: a dict of file info, or None if not exists
1662
+ #
1663
+ def check_remote_file(file, host, opt = 0, logact = 0):
1664
+
1665
+ if not file: return None
1666
+ ms = re.match(r'^(.+)/$', file)
1667
+ if ms: file = ms.group(1) # remove ending '/' in case
1668
+ cmd = "{} {}".format(PgLOG.get_sync_command(host), file)
1669
+ loop = 0
1670
+ while loop < 2:
1671
+ buf = PgLOG.pgsystem(cmd, PgLOG.LOGWRN, CMDRET)
1672
+ if buf or not PgLOG.PGLOG['SYSERR'] or PgLOG.PGLOG['SYSERR'].find(PgLOG.PGLOG['MISSFILE']) > -1: break
1673
+ errmsg = PgLOG.PGLOG['SYSERR']
1674
+ (hstat, msg) = host_down_status(file, host, 0, logact)
1675
+ if hstat: errmsg += "\n" + msg
1676
+ errlog(errmsg, 'R', loop, logact)
1677
+ loop += 1
1678
+
1679
+ if loop > 1: return PgLOG.FAILURE
1680
+ ECNTS['R'] = 0 # reset error count
1681
+ if buf:
1682
+ for line in re.split(r'\n', buf):
1683
+ info = remote_file_stat(line, opt)
1684
+ if info: return info
1685
+
1686
+ return None
1687
+
1688
+ #
1689
+ # local function to get remote file stat
1690
+ #
1691
+ def remote_file_stat(line, opt):
1692
+
1693
+ info = {}
1694
+ items = re.split(r'\s+', line)
1695
+ if len(items) < 5 or items[4] == '.': return None
1696
+ ms = re.match(r'^([d\-])([\w\-]{9})$', items[0])
1697
+ info['isfile'] = (1 if ms and ms.group(1) == "-" else 0)
1698
+ if opt&4: info['mode'] = get_file_mode(ms.group(2))
1699
+ fsize = items[1]
1700
+ if fsize.find(',') > -1: fsize = re.sub(r',', '', fsize)
1701
+ info['data_size'] = int(fsize)
1702
+ info['fname'] = op.basename(items[4])
1703
+ if not opt: return info
1704
+ if opt&17:
1705
+ mdate = PgUtil.format_date(items[2], "YYYY-MM-DD", "YYYY/MM/DD")
1706
+ mtime = items[3]
1707
+ if PgLOG.PGLOG['GMTZ']: (mdate, mtime) = PgUtil.addhour(mdate, mtime, PgLOG.PGLOG['GMTZ'])
1708
+ if opt&1:
1709
+ info['date_modified'] = mdate
1710
+ info['time_modified'] = mtime
1711
+ if opt&16: info['week_day'] = PgUtil.get_weekday(mdate)
1712
+
1713
+ if opt&2: info['logname'] = "rdadata"
1714
+ if opt&8: info['group'] = PgLOG.PGLOG['RDAGRP']
1715
+
1716
+ return info
1717
+
1718
+ #
1719
+ # check and get object file status information
1720
+ #
1721
+ # file: object store File key name
1722
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1723
+ # 1 - get date/time modified (date_modified, time_modfied)
1724
+ # 2 - get file owner's login name (logname)
1725
+ # 4 - get metadata hash
1726
+ # 8 - get group name (group)
1727
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1728
+ # 32 - get checksum (checksum)
1729
+ # 64 - check once, no rechecking
1730
+ #
1731
+ # Return a dict of file info, or None if file not exists
1732
+ #
1733
+ def check_object_file(file, bucket = None, opt = 0, logact = 0):
1734
+
1735
+ if not bucket: bucket = PgLOG.PGLOG['OBJCTBKT']
1736
+ ret = None
1737
+ if not file: return ret
1738
+ cmd = "{} lo {} -b {}".format(OBJCTCMD, file, bucket)
1739
+ ucmd = "{} gm -k {} -b {}".format(OBJCTCMD, file, bucket) if opt&14 else None
1740
+ loop = 0
1741
+ while loop < 2:
1742
+ buf = PgLOG.pgsystem(cmd, PgLOG.LOGWRN, CMDRET)
1743
+ if buf:
1744
+ if re.match(r'^\[\]', buf): break
1745
+ if re.match(r'^\[\{', buf):
1746
+ ary = json.loads(buf)
1747
+ cnt = len(ary)
1748
+ if cnt > 1: return PgLOG.pglog("{}-{}: {} records returned\n{}".format(bucket, file, cnt, buf), logact|PgLOG.ERRLOG)
1749
+ hash = ary[0]
1750
+ uhash = None
1751
+ if ucmd:
1752
+ ubuf = PgLOG.pgsystem(ucmd, PgLOG.LOGWRN, CMDRET)
1753
+ if ubuf and re.match(r'^\{', ubuf): uhash = json.loads(ubuf)
1754
+ ret = object_file_stat(hash, uhash, opt)
1755
+ break
1756
+ if opt&64: return PgLOG.FAILURE
1757
+ errmsg = "Error Execute: {}\n{}".format(cmd, PgLOG.PGLOG['SYSERR'])
1758
+ (hstat, msg) = host_down_status(bucket, OHOST, 0, logact)
1759
+ if hstat: errmsg += "\n" + msg
1760
+ errlog(errmsg, 'O', loop, logact)
1761
+ loop += 1
1762
+
1763
+ if loop > 1: return PgLOG.FAILURE
1764
+ ECNTS['O'] = 0 # reset error count
1765
+ return ret
1766
+
1767
+ #
1768
+ # check an object path status information
1769
+ #
1770
+ # path: object store path name
1771
+ #
1772
+ # Return count of object key names, 0 if not file exists; None if error checking
1773
+ #
1774
+ def check_object_path(path, bucket = None, logact = 0):
1775
+
1776
+ if not bucket: bucket = PgLOG.PGLOG['OBJCTBKT']
1777
+ ret = None
1778
+ if not path: return ret
1779
+ cmd = "{} lo {} -ls -b {}".format(OBJCTCMD, path, bucket)
1780
+ loop = 0
1781
+ while loop < 2:
1782
+ buf = PgLOG.pgsystem(cmd, PgLOG.LOGWRN, CMDRET)
1783
+ if buf:
1784
+ ary = json.loads(buf)
1785
+ return len(ary)
1786
+ errmsg = "Error Execute: {}\n{}".format(cmd, PgLOG.PGLOG['SYSERR'])
1787
+ (hstat, msg) = host_down_status(bucket, OHOST, 0, logact)
1788
+ if hstat: errmsg += "\n" + msg
1789
+ errlog(errmsg, 'O', loop, logact)
1790
+ loop += 1
1791
+
1792
+ ECNTS['O'] = 0 # reset error count
1793
+ return ret
1794
+
1795
+ #
1796
+ # object store function to get file stat
1797
+ #
1798
+ def object_file_stat(hash, uhash, opt):
1799
+
1800
+ info = {'isfile' : 1, 'data_size' : int(hash['Size']), 'fname' : op.basename(hash['Key'])}
1801
+ if not opt: return info
1802
+ if opt&17:
1803
+ ms = re.match(r'^(\d+-\d+-\d+)\s+(\d+:\d+:\d+)', hash['LastModified'])
1804
+ if ms:
1805
+ (mdate, mtime) = ms.groups()
1806
+ if PgLOG.PGLOG['GMTZ']: (mdate, mtime) = PgUtil.addhour(mdate, mtime, PgLOG.PGLOG['GMTZ'])
1807
+ if opt&1:
1808
+ info['date_modified'] = mdate
1809
+ info['time_modified'] = mtime
1810
+ if opt&16: info['week_day'] = PgUtil.get_weekday(mdate)
1811
+ if opt&32:
1812
+ ms = re.match(r'"(.+)"', hash['ETag'])
1813
+ if ms: info['checksum'] = ms.group(1)
1814
+ if uhash:
1815
+ if opt&2: info['logname'] = uhash['user']
1816
+ if opt&4: info['meta'] = uhash
1817
+ if opt&8: info['group'] = uhash['group']
1818
+
1819
+ return info
1820
+
1821
+ #
1822
+ # check and get backup file status information
1823
+ #
1824
+ # file: backup File key name
1825
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1826
+ # 1 - get date/time modified (date_modified, time_modfied)
1827
+ # 2 - get file owner's login name (logname)
1828
+ # 4 - get metadata hash
1829
+ # 8 - get group name (group)
1830
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1831
+ # 64 - rechecking
1832
+ #
1833
+ # Return a dict of file info, or None if file not exists
1834
+ #
1835
+ def check_backup_file(file, endpoint = None, opt = 0, logact = 0):
1836
+
1837
+ ret = None
1838
+ if not file: return ret
1839
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
1840
+ bdir = op.dirname(file)
1841
+ bfile = op.basename(file)
1842
+ cmd = "{} -ls -ep {} -p {} --filter {}".format(BACKCMD, endpoint, bdir, bfile)
1843
+ ccnt = loop = 0
1844
+ while loop < 2:
1845
+ buf = PgLOG.pgsystem(cmd, logact, CMDRET)
1846
+ syserr = PgLOG.PGLOG['SYSERR']
1847
+ if buf:
1848
+ getstat = 0
1849
+ for line in re.split(r'\n', buf):
1850
+ if re.match(r'^(User|-+)\s*\|', line):
1851
+ getstat += 1
1852
+ elif getstat > 1:
1853
+ ret = backup_file_stat(line, opt)
1854
+ if ret: break
1855
+ if ret: break
1856
+ if loop or opt&64 == 0: return ret
1857
+ time.sleep(PgSIG.PGSIG['ETIME'])
1858
+ elif syserr:
1859
+ if syserr.find("Directory '{}' not found on endpoint".format(bdir)) > -1:
1860
+ if loop or opt&64 == 0: return ret
1861
+ time.sleep(PgSIG.PGSIG['ETIME'])
1862
+ elif ccnt < 2 and syserr.find("The connection to the server was broken") > -1:
1863
+ time.sleep(PgSIG.PGSIG['ETIME'])
1864
+ ccnt += 1
1865
+ continue
1866
+ else:
1867
+ if opt&64 == 0: return PgLOG.FAILURE
1868
+ errmsg = "Error Execute: {}\n{}".format(cmd, syserr)
1869
+ (hstat, msg) = host_down_status('', QHOSTS[endpoint], 0, logact)
1870
+ if hstat: errmsg += "\n" + msg
1871
+ errlog(errmsg, 'B', loop, logact)
1872
+ loop += 1
1873
+
1874
+ if ret: ECNTS['B'] = 0 # reset error count
1875
+ return ret
1876
+
1877
+ #
1878
+ # backup store function to get file stat
1879
+ #
1880
+ def backup_file_stat(line, opt):
1881
+
1882
+ info = {}
1883
+ items = re.split(r'[\s\|]+', line)
1884
+ if len(items) < 8: return None
1885
+ info['isfile'] = (1 if items[6] == 'file' else 0)
1886
+ info['data_size'] = int(items[3])
1887
+ info['fname'] = items[7]
1888
+ if not opt: return info
1889
+ if opt&17:
1890
+ mdate = items[4]
1891
+ mtime = items[5]
1892
+ ms = re.match(r'^(\d+:\d+:\d+)', mtime)
1893
+ if ms: mtime = ms.group(1)
1894
+ if PgLOG.PGLOG['GMTZ']: (mdate, mtime) = PgUtil.addhour(mdate, mtime, PgLOG.PGLOG['GMTZ'])
1895
+ if opt&1:
1896
+ info['date_modified'] = mdate
1897
+ info['time_modified'] = mtime
1898
+ if opt&16: info['week_day'] = PgUtil.get_weekday(mdate)
1899
+ if opt&2: info['logname'] = items[0]
1900
+ if opt&4: info['mode'] = get_file_mode(items[2])
1901
+ if opt&8: info['group'] = items[1]
1902
+
1903
+ return info
1904
+
1905
+ #
1906
+ # check and get a file status information inside a tar file
1907
+ #
1908
+ # file: File name to be checked
1909
+ # tfile: the tar file name
1910
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1911
+ # 1 - get date/time modified (date_modified, time_modfied)
1912
+ # 2 - get file owner's login name (logname)
1913
+ # 4 - get permission mode in 3 octal digits (mode)
1914
+ # 8 - get group name (group)
1915
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1916
+ #
1917
+ # Return a dict of file info, or None if file not exists
1918
+ #
1919
+ def check_tar_file(file, tfile, opt = 0, logact = 0):
1920
+
1921
+ ret = None
1922
+ if not (file and tfile): return ret
1923
+
1924
+ for loop in range(2):
1925
+ buf = PgLOG.pgsystem("tar -tvf {} {}".format(tfile, file), PgLOG.LOGWRN, CMDRET)
1926
+ if buf or not PgLOG.PGLOG['SYSERR'] or PgLOG.PGLOG['SYSERR'].find('Not found in archive') > -1: break
1927
+
1928
+ errmsg = PgLOG.PGLOG['SYSERR']
1929
+ (hstat, msg) = host_down_status(tfile, LHOST, 0, logact)
1930
+ errlog(errmsg, 'L', loop, logact)
1931
+
1932
+ if loop > 0: return PgLOG.FAILURE
1933
+ if buf:
1934
+ for line in re.split(r'\n', buf):
1935
+ ret = tar_file_stat(line, opt)
1936
+ if ret: break
1937
+ ECNTS['L'] = 0 # reset error count
1938
+
1939
+ return ret
1940
+
1941
+ #
1942
+ # local function to get file stat in a tar file
1943
+ #
1944
+ def tar_file_stat(line, opt):
1945
+
1946
+ items = re.split(r'\s+', line)
1947
+ if len(items) < 6: return None
1948
+ ms = re.match(r'^([d\-])([\w\-]{9})$', items[0])
1949
+ if not ms: return None
1950
+ info = {}
1951
+ info['isfile'] = (1 if ms and ms.group(1) == "-" else 0)
1952
+ info['data_size'] = int(items[2])
1953
+ info['fname'] = op.basename(items[5])
1954
+ if not opt: return info
1955
+ if opt&4: info['mode'] = get_file_mode(ms.group(2))
1956
+ if opt&17:
1957
+ mdate = items[3]
1958
+ mtime = items[4]
1959
+ if PgLOG.PGLOG['GMTZ']: (mdate, mtime) = PgUtil.addhour(mdate, mtime, PgLOG.PGLOG['GMTZ'])
1960
+ if opt&1:
1961
+ info['date_modified'] = mdate
1962
+ info['time_modified'] = mtime
1963
+ if opt&16: info['week_day'] = PgUtil.get_weekday(mdate)
1964
+
1965
+ if opt&10:
1966
+ ms = re.match(r'^(\w+)/(\w+)', items[1])
1967
+ if ms:
1968
+ if opt&2: info['logname'] = ms.group(1)
1969
+ if opt&8: info['group'] = ms.group(2)
1970
+
1971
+ return info
1972
+
1973
+ #
1974
+ # check and get a file status information on ftp server
1975
+ #
1976
+ # file: File name to be checked
1977
+ # name: login user name
1978
+ # pswd: login password
1979
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
1980
+ # 1 - get date/time modified (date_modified, time_modfied)
1981
+ # 2 - get file owner's login name (logname)
1982
+ # 4 - get permission mode in 3 octal digits (mode)
1983
+ # 8 - get group name (group)
1984
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
1985
+ #
1986
+ # Return a dict of file info, or None if file not exists
1987
+ #
1988
+ def check_ftp_file(file, opt = 0, name = None, pswd = None, logact = 0):
1989
+
1990
+ if not file: return None
1991
+
1992
+ ms = re.match(r'^(.+)/$', file)
1993
+ if ms: file = ms.group(1) # remove ending '/' in case
1994
+ cmd = "ncftpls -l "
1995
+ if name: cmd += "-u {} ".format(name)
1996
+ if pswd: cmd += "-p {} ".format(pswd)
1997
+ fname = op.basename(file)
1998
+
1999
+
2000
+ for loop in range(2):
2001
+ buf = PgLOG.pgsystem(cmd + file, PgLOG.LOGWRN, CMDRET)
2002
+ if buf: break
2003
+ if PgLOG.PGLOG['SYSERR']:
2004
+ errlog(PgLOG.PGLOG['SYSERR'], 'O', loop, logact|PgLOG.LOGERR)
2005
+ if loop == 0: file = op.dirname(file) + '/'
2006
+
2007
+ if loop > 1: return PgLOG.FAILURE
2008
+ for line in re.split(r'\n', buf):
2009
+ if not line or line.find(fname) < 0: continue
2010
+ info = ftp_file_stat(line, opt)
2011
+ if info: return info
2012
+
2013
+ return None
2014
+
2015
+ #
2016
+ # local function to get stat of a file on ftp server
2017
+ #
2018
+ def ftp_file_stat(line, opt):
2019
+
2020
+ items = re.split(r'\s+', line)
2021
+ if len(items) < 9: return None
2022
+ ms = re.match(r'^([d\-])([\w\-]{9})$', items[0])
2023
+ info = {}
2024
+ info['isfile'] = (1 if ms and ms.group(1) == "-" else 0)
2025
+ info['data_size'] = int(items[4])
2026
+ info['fname'] = op.basename(items[8])
2027
+ if not opt: return info
2028
+ if opt&4: info['mode'] = get_file_mode(ms.group(2))
2029
+ if opt&17:
2030
+ dy = int(items[6])
2031
+ mn = PgUtil.get_month(items[5])
2032
+ if re.match(r'^\d+$', items[7]):
2033
+ yr = int(items[7])
2034
+ mtime = "00:00:00"
2035
+ else:
2036
+ mtime = items[7] + ":00"
2037
+ cdate = PgUtil.curdate()
2038
+ ms = re.match(r'^(\d+)-(\d\d)', cdate)
2039
+ if ms:
2040
+ yr = int(ms.group(1))
2041
+ cm = int(ms.group(2)) # current month
2042
+ if cm < mn: yr -= 1 # previous year
2043
+
2044
+ mdate = "{}-{:02}-{:02}".format(yr, mn, dy)
2045
+ if opt&1:
2046
+ info['date_modified'] = mdate
2047
+ info['time_modified'] = mtime
2048
+ if opt&16: info['week_day'] = PgUtil.get_weekday(mdate)
2049
+
2050
+ if opt&2: info['logname'] = items[2]
2051
+ if opt&8: info['group'] = items[3]
2052
+
2053
+ return info
2054
+
2055
+ #
2056
+ # get an array of directories/files under given dir on a given host name (including local host)
2057
+ #
2058
+ # dir: directory name to be listed
2059
+ # host: host name the directory on, default to LHOST
2060
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
2061
+ # 1 - get date/time modified (date_modified, time_modfied)
2062
+ # 2 - get file owner's login name (logname)
2063
+ # 4 - get permission mode in 3 octal digits (mode)
2064
+ # 8 - get group name (group)
2065
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
2066
+ # 32 - get checksum (checksum), work for local file only
2067
+ #
2068
+ # Return: a dict with filenames as keys None if empty directory
2069
+ #
2070
+ def rda_glob(dir, host, opt = 0, logact = 0):
2071
+
2072
+ shost = strip_host_name(host)
2073
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
2074
+ return local_glob(dir, opt, logact)
2075
+ elif PgUtil.pgcmp(shost, OHOST, 1) == 0:
2076
+ return object_glob(dir, None, opt, logact)
2077
+ elif PgUtil.pgcmp(shost, BHOST, 1) == 0:
2078
+ return backup_glob(dir, None, opt, logact)
2079
+ else:
2080
+ return remote_glob(dir, host, opt, logact)
2081
+
2082
+ #
2083
+ # get an array of directories/files under given dir on local host
2084
+ #
2085
+ # dir: directory name to be listed
2086
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
2087
+ # 1 - get date/time modified (date_modified, time_modfied)
2088
+ # 2 - get file owner's login name (logname)
2089
+ # 4 - get permission mode in 3 octal digits (mode)
2090
+ # 8 - get group name (group)
2091
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
2092
+ # 32 - get checksum (checksum), work for local file only
2093
+ #
2094
+ # 256 - get files only and ignore directories
2095
+ #
2096
+ # Return: dict with filenames as keys or None if empty directory
2097
+ #
2098
+
2099
+ def local_glob(dir, opt = 0, logact = 0):
2100
+
2101
+ flist = {}
2102
+ if not re.search(r'[*?]', dir):
2103
+ if op.exists(dir):
2104
+ dir = PgLOG.join_paths(dir, "*")
2105
+ else:
2106
+ dir += "*"
2107
+
2108
+ for file in glob.glob(dir):
2109
+ info = check_local_file(file, opt, logact)
2110
+ if info and (info['isfile'] or not 256&opt): flist[file] = info
2111
+
2112
+ return flist
2113
+
2114
+ #
2115
+ # check and get file status information of a file on remote host
2116
+ #
2117
+ # dir: remote directory name
2118
+ # host: host name the directory on, default to LHOST
2119
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
2120
+ # 1 - get date/time modified (date_modified, time_modfied)
2121
+ # 2 - file owner's login name (logname), assumed 'rdadata'
2122
+ # 4 - get permission mode in 3 octal digits (mode)
2123
+ # 8 - get group name (group), assumed 'dss'
2124
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
2125
+ #
2126
+ # Return: dict with filenames as keys or None if empty directory
2127
+ #
2128
+ def remote_glob(dir, host, opt = 0, logact = 0):
2129
+
2130
+ flist = {}
2131
+ if not re.search(r'/$', dir): dir += '/'
2132
+ buf = PgLOG.pgsystem(PgLOG.get_sync_command(host) + " dir", PgLOG.LOGWRN, CMDRET)
2133
+ if not buf:
2134
+ if PgLOG.PGLOG['SYSERR'] and PgLOG.PGLOG['SYSERR'].find(PgLOG.PGLOG['MISSFILE']) < 0:
2135
+ errlog("{}-{}: Error list directory\n{}".format(host, dir, PgLOG.PGLOG['SYSERR']), 'R', 1, logact)
2136
+ return flist
2137
+
2138
+ for line in re.split(r'\n', buf):
2139
+ info = remote_file_stat(line, opt)
2140
+ if info: flist[dir + info['fname']] = info
2141
+
2142
+ return flist
2143
+
2144
+ #
2145
+ # check and get muiltiple object store file status information
2146
+ #
2147
+ # dir: object directory name
2148
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
2149
+ # 1 - get date/time modified (date_modified, time_modfied)
2150
+ # 2 - get file owner's login name (logname)
2151
+ # 8 - get group name (group)
2152
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
2153
+ #
2154
+ # Return: a dict with filenames as keys, or None if not exists
2155
+ #
2156
+ def object_glob(dir, bucket = None, opt = 0, logact = 0):
2157
+
2158
+ flist = {}
2159
+ if not bucket: bucket = PgLOG.PGLOG['OBJCTBKT']
2160
+ ms = re.match(r'^(.+)/$', dir)
2161
+ if ms: dir = ms.group(1)
2162
+ cmd = "{} lo {} -b {}".format(OBJCTCMD, dir, bucket)
2163
+ ary = err = None
2164
+ buf = PgLOG.pgsystem(cmd, PgLOG.LOGWRN, CMDRET)
2165
+ if buf:
2166
+ if re.match(r'^\[\{', buf):
2167
+ ary = json.loads(buf)
2168
+ elif not re.match(r'^\[\]', buf):
2169
+ err = "{}\n{}".format(PgLOG.PGLOG['SYSERR'], buf)
2170
+ else:
2171
+ err = PgLOG.PGLOG['SYSERR']
2172
+ if not ary:
2173
+ if err:
2174
+ errlog("{}-{}-{}: Error list files\n{}".format(OHOST, bucket, dir, err), 'O', 1, logact)
2175
+ return PgLOG.FAILURE
2176
+ else:
2177
+ return flist
2178
+
2179
+ for hash in ary:
2180
+ uhash = None
2181
+ if opt&10:
2182
+ ucmd = "{} gm -l {} -b {}".format(OBJCTCMD, hash['Key'], bucket)
2183
+ ubuf = PgLOG.pgsystem(ucmd, PgLOG.LOGWRN, CMDRET)
2184
+ if ubuf and re.match(r'^\{.+', ubuf): uhash = json.loads(ubuf)
2185
+ info = object_file_stat(hash, uhash, opt)
2186
+ if info: flist[hash['Key']] = info
2187
+
2188
+ return flist
2189
+
2190
+ #
2191
+ # check and get muiltiple Quasar backup file status information
2192
+ #
2193
+ # dir: backup path
2194
+ # opt: 0 - get data size only (fname, data_size, isfile), fname is the file basename
2195
+ # 1 - get date/time modified (date_modified, time_modfied)
2196
+ # 2 - get file owner's login name (logname)
2197
+ # 8 - get group name (group)
2198
+ # 16 - get week day 0-Sunday, 1-Monday (week_day)
2199
+ # 64 - rechecking
2200
+ #
2201
+ # Return: a dict with filenames as keys, or None if not exists
2202
+ #
2203
+ def backup_glob(dir, endpoint = None, opt = 0, logact = 0):
2204
+
2205
+ if not dir: return None
2206
+ if not endpoint: endpoint = PgLOG.PGLOG['BACKUPEP']
2207
+ cmd = "{} -ls -ep {} -p {}".format(BACKCMD, endpoint, dir)
2208
+ flist = {}
2209
+ for loop in range(2):
2210
+ buf = PgLOG.pgsystem(cmd, logact, CMDRET)
2211
+ syserr = PgLOG.PGLOG['SYSERR']
2212
+ if buf:
2213
+ getstat = 0
2214
+ for line in re.split(r'\n', buf):
2215
+ if re.match(r'^(User|-+)\s*\|', line):
2216
+ getstat += 1
2217
+ elif getstat > 1:
2218
+ info = backup_file_stat(line, opt)
2219
+ if info: flist[info['fname']] = info
2220
+ if flist: break
2221
+ if loop or opt&64 == 0: return None
2222
+ time.sleep(PgSIG.PGSIG['ETIME'])
2223
+ elif syserr:
2224
+ if syserr.find("Directory '{}' not found on endpoint".format(dir)) > -1:
2225
+ if loop or opt&64 == 0: return None
2226
+ time.sleep(PgSIG.PGSIG['ETIME'])
2227
+ else:
2228
+ if opt&64 == 0: return PgLOG.FAILURE
2229
+ errmsg = "Error Execute: {}\n{}".format(cmd, syserr)
2230
+ (hstat, msg) = host_down_status('', QHOSTS[endpoint], 0, logact)
2231
+ if hstat: errmsg += "\n" + msg
2232
+ errlog(errmsg, 'B', loop, logact)
2233
+
2234
+ if flist:
2235
+ ECNTS['B'] = 0 # reset error count
2236
+ return flist
2237
+ else:
2238
+ return PgLOG.FAILURE
2239
+
2240
+ #
2241
+ # local function to get file/directory mode for given permission string, for example, rw-rw-r--
2242
+ #
2243
+ def get_file_mode(perm):
2244
+
2245
+ mbits = [4, 2, 1]
2246
+ mults = [64, 8, 1]
2247
+ plen = len(perm)
2248
+ if plen == 4:
2249
+ perm = perm[1:]
2250
+ plen = 3
2251
+ mode = 0
2252
+ for i in range(3):
2253
+ for j in range(3):
2254
+ pidx = 3*i+j
2255
+ if pidx < plen and perm[pidx] != "-": mode += mults[i]*mbits[j]
2256
+
2257
+ return mode
2258
+
2259
+ #
2260
+ # Evaluate md5 checksum
2261
+ #
2262
+ # file: file name for MD5 checksum
2263
+ # count: defined if filename is a array
2264
+ #
2265
+ # Return: one or a array of 128-bits md5 'fingerprint' None if failed
2266
+ #
2267
+ def get_md5sum(file, count = 0, logact = 0):
2268
+
2269
+ cmd = MD5CMD + ' '
2270
+
2271
+ if count > 0:
2272
+ checksum = [None]*count
2273
+ for i in range(count):
2274
+ chksm = PgLOG.pgsystem(cmd + file[i], logact, 20)
2275
+ if chksm:
2276
+ ms = re.search(r'(\w{32})', chksm)
2277
+ if ms: checksum[i] = ms.group(1)
2278
+ else:
2279
+ chksm = PgLOG.pgsystem(cmd + file, logact, 20)
2280
+ checksum = None
2281
+ if chksm:
2282
+ ms = re.search(r'(\w{32})', chksm)
2283
+ if ms: checksum = ms.group(1)
2284
+
2285
+ return checksum
2286
+
2287
+ #
2288
+ # Evaluate md5 checksums and compare them for two given files
2289
+ #
2290
+ # file1, file2: file names
2291
+ #
2292
+ # Return: 0 if same and 1 if not
2293
+ #
2294
+ def compare_md5sum(file1, file2, logact = 0):
2295
+
2296
+ if op.isdir(file1) or op.isdir(file2):
2297
+ files1 = get_directory_files(file1)
2298
+ fcnt1 = len(files1) if files1 else 0
2299
+ files2 = get_directory_files(file2)
2300
+ fcnt2 = len(files2) if files2 else 0
2301
+ if fcnt1 != fcnt2: return 1
2302
+ chksm1 = get_md5sum(files1, fcnt1, logact)
2303
+ chksm1 = ''.join(chksm1)
2304
+ chksm2 = get_md5sum(files1, fcnt2, logact)
2305
+ chksm2 = ''.join(chksm2)
2306
+ else:
2307
+ chksm1 = get_md5sum(file1, 0, logact)
2308
+ chksm2 = get_md5sum(file2, 0, logact)
2309
+
2310
+ return (0 if (chksm1 and chksm2 and chksm1 == chksm2) else 1)
2311
+
2312
+ #
2313
+ # change local directory to todir, and return odir upon success
2314
+ #
2315
+ def change_local_directory(todir, logact = 0):
2316
+
2317
+ if logact:
2318
+ lact = logact&~(PgLOG.EXITLG|PgLOG.ERRLOG)
2319
+ else:
2320
+ logact = lact = PgLOG.LOGWRN
2321
+ if not op.isdir(todir):
2322
+ if op.isfile(todir): return errlog(todir + ": is file, cannot change directory", 'L', 1, logact)
2323
+ if not make_local_directory(todir, logact): return PgLOG.FAILURE
2324
+
2325
+ odir = PgLOG.PGLOG['CURDIR']
2326
+ if todir == odir:
2327
+ PgLOG.pglog(todir + ": in Directory", lact)
2328
+ return odir
2329
+ try:
2330
+ os.chdir(todir)
2331
+ except Exception as e:
2332
+ return errlog(str(e), 'L', 1, logact)
2333
+ else:
2334
+ if not op.isabs(todir): todir = os.getcwd()
2335
+ PgLOG.PGLOG['CURDIR'] = todir
2336
+ PgLOG.pglog(todir + ": Change to Directory", lact)
2337
+
2338
+ return odir
2339
+
2340
+ #
2341
+ # record the directory for the deleted file
2342
+ # pass in empty dir to turn the recording delete directory on
2343
+ #
2344
+ def record_delete_directory(dir, val):
2345
+
2346
+ global DIRLVLS
2347
+
2348
+ if dir is None:
2349
+ if isinstance(val, int):
2350
+ DIRLVLS = val
2351
+ elif re.match(r'^\d+$'):
2352
+ DIRLVLS = int(val)
2353
+ elif dir and not re.match(r'^(\.|\./|/)$', dir) and dir not in DELDIRS:
2354
+ DELDIRS[dir] = val
2355
+
2356
+ #
2357
+ # remove the recorded delete directory if it is empty
2358
+ #
2359
+ def clean_delete_directory(logact = 0):
2360
+
2361
+ global DIRLVLS, DELDIRS
2362
+
2363
+ if not DIRLVLS: return
2364
+ if logact:
2365
+ lact = logact&~(PgLOG.EXITLG)
2366
+ else:
2367
+ logact = lact = PgLOG.LOGWRN
2368
+ lvl = DIRLVLS
2369
+ DIRLVLS = 0 # set to 0 to stop recording directory
2370
+ while lvl > 0:
2371
+ lvl -= 1
2372
+ dirs = {}
2373
+ for dir in DELDIRS:
2374
+ host = DELDIRS[dir]
2375
+ dinfo = (dir if host == LHOST else "{}-{}".format(host, dir))
2376
+ dstat = rda_empty_directory(dir, DELDIRS[dir])
2377
+ if dstat == 0:
2378
+ if delete_rda_file(dir, host, logact):
2379
+ PgLOG.pglog(dinfo + ": Empty directory removed", lact)
2380
+ elif dstat > 0:
2381
+ if dstat == 1 and lvl > 0: PgLOG.pglog(dinfo + ": Directory not empty yet", lact)
2382
+ continue
2383
+
2384
+ if lvl: dirs[op.dirname(dir)] = host
2385
+
2386
+ if not dirs: break
2387
+ DELDIRS = dirs
2388
+
2389
+ DELDIRS = {} # empty cache afterward
2390
+
2391
+ #
2392
+ # remove the empty given directory and its all subdirectories
2393
+ #
2394
+ # return 1 if empty dirctory removed 0 otherwise
2395
+ #
2396
+ def clean_empty_directory(dir, host, logact = 0):
2397
+
2398
+ if not dir: return 0
2399
+
2400
+ dirs = rda_glob(dir, host)
2401
+ cnt = 0
2402
+ if logact:
2403
+ lact = logact&~PgLOG.EXITLG
2404
+ else:
2405
+ lact = logact = PgLOG.LOGWRN
2406
+
2407
+ if dirs:
2408
+ for name in dirs:
2409
+ cnt += 1
2410
+ if dirs[name]['isfile']: continue
2411
+ cnt -= clean_empty_directory(name, host, logact)
2412
+
2413
+ dinfo = (dir if same_hosts(host, LHOST) else "{}-{}".format(host, dir))
2414
+ if cnt == 0:
2415
+ if delete_rda_file(dir, host, logact):
2416
+ PgLOG.pglog(dinfo + ": Empty directory removed", lact)
2417
+ return 1
2418
+ else:
2419
+ PgLOG.pglog(dinfo + ": Directory not empty yet", lact)
2420
+
2421
+ return 0
2422
+
2423
+
2424
+ #
2425
+ # check if given directory is empty
2426
+ #
2427
+ # Return: 0 if empty directory, 1 if not empty and -1 if invalid directory
2428
+ #
2429
+ def rda_empty_directory(dir, host):
2430
+
2431
+ shost = strip_host_name(host)
2432
+
2433
+ if PgUtil.pgcmp(shost, LHOST, 1) == 0:
2434
+ return local_empty_directory(dir)
2435
+ else:
2436
+ return remote_empty_directory(dir, host)
2437
+
2438
+ #
2439
+ # return 0 if empty local directory, 1 if not; -1 if cannot remove
2440
+ #
2441
+ def local_empty_directory(dir):
2442
+
2443
+ if not op.isdir(dir): return -1
2444
+ if is_root_directory(dir, 'L'): return 2
2445
+ if not re.search(r'/$', dir): dir += '/'
2446
+ dir += '*'
2447
+ return (1 if glob.glob(dir) else 0)
2448
+
2449
+ #
2450
+ # return 0 if empty remote directory, 1 if not; -1 if cannot remove
2451
+ #
2452
+ def remote_empty_directory(dir, host):
2453
+
2454
+ if is_root_directory(dir, 'R', host): return 2
2455
+ if not re.search(r'/$', dir): dir += '/'
2456
+ buf = PgLOG.pgsystem("{} {}".format(PgLOG.get_sync_command(host), dir), PgLOG.LOGWRN, CMDRET)
2457
+ if not buf: return -1
2458
+
2459
+ for line in re.split(r'\n', buf):
2460
+ if remote_file_stat(line, 0): return 1
2461
+
2462
+ return 0
2463
+
2464
+ #
2465
+ # get sizes of files on a given host
2466
+ #
2467
+ # files: file names to get sizes
2468
+ # host: host name the file on, default to LHOST
2469
+ #
2470
+ # return: array of file sizes size is -1 if file does not exist
2471
+ #
2472
+ def rda_file_sizes(files, host, logact = 0):
2473
+
2474
+ sizes = []
2475
+ for file in files: sizes.append(rda_file_size(file, host, 2, logact))
2476
+
2477
+ return sizes
2478
+
2479
+ #
2480
+ # get sizes of local files
2481
+ #
2482
+ # files: file names to get sizes
2483
+ #
2484
+ # return: array of file sizes size is -1 if file does not exist
2485
+ #
2486
+ def local_file_sizes(files, logact = 0):
2487
+
2488
+ sizes = []
2489
+ for file in files: sizes.append(local_file_size(file, 6, logact))
2490
+
2491
+ return sizes
2492
+
2493
+ #
2494
+ # check if a file on a given host is empty or too small to be considered valid
2495
+ #
2496
+ # file: file name to be checked
2497
+ # host: host name the file on, default to LHOST
2498
+ # opt: 1 - to remove empty file
2499
+ # 2 - show message for empty file
2500
+ # 4 - show message for non-existing file
2501
+ #
2502
+ # return: file size in unit of byte
2503
+ # 0 - empty file or small file, with size < PgLOG.PGLOG['MINSIZE']
2504
+ # -1 - file not exists
2505
+ # -2 - error check file
2506
+ #
2507
+ def rda_file_size(file, host, opt = 0, logact = 0):
2508
+
2509
+ info = check_rda_file(file, host, 0, logact)
2510
+ if info:
2511
+ if info['isfile'] and info['data_size'] < PgLOG.PGLOG['MINSIZE']:
2512
+ if opt:
2513
+ if opt&2: errlog("{}-{}: {} file".format(host, file, ("Too small({}B)".format(info['data_size']) if info['data_size'] > 0 else "Empty")),
2514
+ 'O', 1, logact)
2515
+ if opt&1: delete_rda_file(file, host, logact)
2516
+ return 0
2517
+ else:
2518
+ return info['data_size'] # if not regular file or not empty
2519
+
2520
+ elif info != None:
2521
+ return -2 # error access
2522
+ else:
2523
+ if opt&4: errlog("{}-{}: {}".format(host, file, PgLOG.PGLOG['MISSFILE']), 'O', 1, logact)
2524
+ return -1 # file not exist
2525
+
2526
+ #
2527
+ # check if a local file is empty or too small to be considered valid
2528
+ #
2529
+ # file: file name to be checked
2530
+ # opt: 1 - to remove empty file
2531
+ # 2 - show message for empty file
2532
+ # 4 - show message for non-existing file
2533
+ #
2534
+ # return: file size in unit of byte
2535
+ # 0 - empty file or small file, with size < PgLOG.PGLOG['MINSIZE']
2536
+ # -1 - file not exists
2537
+ # -2 - error check file
2538
+ #
2539
+ def local_file_size(file, opt = 0, logact = 0):
2540
+
2541
+ if not op.isfile(file):
2542
+ if opt&4: lmsg(file, PgLOG.PGLOG['MISSFILE'], logact)
2543
+ return -1 # file not eixsts
2544
+
2545
+ info = check_local_file(file, 0, logact)
2546
+ if info:
2547
+ if info['isfile'] and info['data_size'] < PgLOG.PGLOG['MINSIZE']:
2548
+ if opt:
2549
+ if opt&2: lmsg(file, ("Too small({}B)".format(info['data_size']) if info['data_size'] > 0 else "Empty file") , logact)
2550
+ if opt&1: delete_local_file(file, logact)
2551
+ return 0
2552
+ else:
2553
+ return info['data_size'] # if not regular file or not empty
2554
+ elif info != None:
2555
+ return -2 # error check file
2556
+
2557
+ #
2558
+ # compress/uncompress a single local file
2559
+ #
2560
+ # ifile: file name to be compressed/uncompressed
2561
+ # fmt: archive format
2562
+ # act: 0 - uncompress
2563
+ # 1 - compress
2564
+ # 2 - get uncompress file name
2565
+ # 3 - get compress file name
2566
+ # return: array of new file name and archive format if changed otherwise original one
2567
+ #
2568
+ def compress_local_file(ifile, fmt = None, act = 0, logact = 0):
2569
+
2570
+ ms = re.match(r'^(.+)\.({})'.format(CMPSTR), ifile)
2571
+ if ms:
2572
+ ofile = ms.group(1)
2573
+ else:
2574
+ ofile = ifile
2575
+
2576
+ if fmt:
2577
+ if act&1:
2578
+ for ext in PGCMPS:
2579
+ if re.search(r'(^|\.)({})(\.|$)'.format(ext), fmt, re.I):
2580
+ ofile += '.' + ext
2581
+ break
2582
+ else:
2583
+ ms = re.search(r'(^|\.)({})$'.format(CMPSTR), fmt, re.I)
2584
+ if ms: fmt = re.sub(r'{}{}$'.format(ms.group(1), ms.group(2)), '', fmt, 1)
2585
+
2586
+ if act < 2 and ifile != ofile: convert_files(ofile, ifile, 0, logact)
2587
+
2588
+ return (ofile, fmt)
2589
+
2590
+ #
2591
+ # get file archive format from a givn file name; None if not found
2592
+ #
2593
+ def get_file_format(fname):
2594
+
2595
+ ms = re.search(r'\.({})$'.format(TARSTR), fname, re.I)
2596
+ if ms: return PGTARS[ms.group(1)][2]
2597
+
2598
+ ms = re.search(r'\.({})$'.format(CMPSTR), fname, re.I)
2599
+ if ms: return PGCMPS[ms.group(1)][2]
2600
+
2601
+ return None
2602
+
2603
+ #
2604
+ # tar/untar mutliple local file into/from a single tar/tar.gz/tgz/zip file
2605
+ #
2606
+ # tfile: tar file name to be tar/untarred
2607
+ # files: member file names in the tar file
2608
+ # fmt: archive format (defaults to tar file name extension must be defined in PGTARS
2609
+ # act: 0 - untar
2610
+ # 1 - tar
2611
+ # return: PgLOG.SUCCESS upon successful PgLOG.FAILURE otherwise
2612
+ #
2613
+ def tar_local_file(tfile, files, fmt, act, logact = 0):
2614
+
2615
+ if not fmt:
2616
+ ms = re.search(r'\.({})$'.format(TARSTR), tfile, re.I)
2617
+ if ms: fmt = ms.group(1)
2618
+ logact |= PgLOG.ERRLOG
2619
+
2620
+ if not fmt: return PgLOG.pglog(tfile + ": Miss archive format", logact)
2621
+ if fmt not in PGTARS: return PgLOG.pglog(tfile + ": unknown format fmt provided", logact)
2622
+ tarray = PGTARS[fmt]
2623
+
2624
+ if not act: #untar member files
2625
+ cmd = "{} {}".format(tarray[1], tfile)
2626
+ if files: cmd += ' ' + ' '.join(files)
2627
+ else:
2628
+ if not files: return PgLOG.pglog(tfile + ": Miss member file to archive", logact)
2629
+ cmd = "{} {} {}".format(tarray[0], tfile, ' '.join(files))
2630
+
2631
+ return PgLOG.pgsystem(cmd, logact, 7)
2632
+
2633
+ #
2634
+ # get local file archive format by checking extension of given local file name
2635
+ #
2636
+ # file: local file name
2637
+ #
2638
+ def local_archive_format(file):
2639
+
2640
+ ms = re.search(r'\.({})$'.format(CMPSTR), file)
2641
+ if ms:
2642
+ fmt = ms.group(1)
2643
+ if re.search(r'\.tar\.{}$'.format(fmt), file):
2644
+ return "TAR." + fmt.upper()
2645
+ else:
2646
+ return fmt.upper()
2647
+ elif re.search(r'\.tar$', file):
2648
+ return "TAR"
2649
+
2650
+ return ''
2651
+
2652
+ #
2653
+ # local function to show message with full local file path
2654
+ #
2655
+ def lmsg(file, msg, logact = 0):
2656
+
2657
+ if not op.isabs(file): file = PgLOG.join_paths(os.getcwd(), file)
2658
+
2659
+ return errlog("{}: {}".format(file, msg), 'L', 1, logact)
2660
+
2661
+ #
2662
+ # check if given path is executable locally
2663
+ #
2664
+ # return PgLOG.SUCCESS if yes PgLOG.FAILURE if not
2665
+ #
2666
+ def check_local_executable(path, actstr = '', logact = 0):
2667
+
2668
+ if os.access(path, os.W_OK): return PgLOG.SUCCESS
2669
+ if check_local_accessible(path, actstr, logact):
2670
+ if actstr: actstr += '-'
2671
+ errlog("{}{}: Accessible, but Unexecutable on'{}'".format(actstr, path, PgLOG.PGLOG['HOSTNAME']), 'L', 1, logact)
2672
+
2673
+ return PgLOG.FAILURE
2674
+
2675
+
2676
+ #
2677
+ # check if given path is writable locally
2678
+ #
2679
+ # return PgLOG.SUCCESS if yes PgLOG.FAILURE if not
2680
+ #
2681
+ def check_local_writable(path, actstr = '', logact = 0):
2682
+
2683
+ if os.access(path, os.W_OK): return PgLOG.SUCCESS
2684
+ if check_local_accessible(path, actstr, logact):
2685
+ if actstr: actstr += '-'
2686
+ errlog("{}{}: Accessible, but Unwritable on'{}'".format(actstr, path, PgLOG.PGLOG['HOSTNAME']), 'L', 1, logact)
2687
+
2688
+ return PgLOG.FAILURE
2689
+
2690
+ #
2691
+ # check if given path is accessible locally
2692
+ #
2693
+ # return PgLOG.SUCCESS if yes, PgLOG.FAILURE if not
2694
+ #
2695
+ def check_local_accessible(path, actstr = '', logact = 0):
2696
+
2697
+ if os.access(path, os.F_OK): return PgLOG.SUCCESS
2698
+ if actstr: actstr += '-'
2699
+ errlog("{}{}: Unaccessible on '{}'".format(actstr, path, PgLOG.PGLOG['HOSTNAME']), 'L', 1, logact)
2700
+ return PgLOG.FAILURE
2701
+
2702
+ #
2703
+ # check if given webfile under PgLOG.PGLOG['DSSDATA'] is writable
2704
+ #
2705
+ # return PgLOG.SUCCESS if yes PgLOG.FAILURE if not
2706
+ #
2707
+ def check_webfile_writable(action, wfile, logact = 0):
2708
+
2709
+ ms = re.match(r'^({}/\w+)'.format(PgLOG.PGLOG['DSSDATA']), wfile)
2710
+ if ms:
2711
+ return check_local_writable(ms.group(1), "{} {}".format(action, wfile), logact)
2712
+ else:
2713
+ return PgLOG.SUCCESS # do not need check
2714
+
2715
+ #
2716
+ # convert the one file to another via uncompress, move/copy, and/or compress
2717
+ #
2718
+ def convert_files(ofile, ifile, keep = 0, logact = 0):
2719
+
2720
+ if ofile == ifile: return PgLOG.SUCCESS
2721
+ oname = ofile
2722
+ iname = ifile
2723
+
2724
+ if keep: kfile = ifile + ".keep"
2725
+
2726
+ oext = iext = None
2727
+ for ext in PGCMPS:
2728
+ if oext is None:
2729
+ ms = re.match(r'^(.+)\.{}$'.format(ext), ofile)
2730
+ if ms:
2731
+ oname = ms.group(1)
2732
+ oext = ext
2733
+ if iext is None:
2734
+ ms = re.match(r'^(.+)\.{}$'.format(ext), ifile)
2735
+ if ms:
2736
+ iname = ms.group(1)
2737
+ iext = ext
2738
+
2739
+ if iext and oext and oext == iext:
2740
+ oext = iext = None
2741
+ iname = ifile
2742
+ oname = ofile
2743
+
2744
+ if iext: # uncompress
2745
+ if keep:
2746
+ if iext == 'zip':
2747
+ kfile = ifile
2748
+ else:
2749
+ local_copy_local(kfile, ifile, logact)
2750
+
2751
+ if PgLOG.pgsystem("{} {}".format(PGCMPS[iext][1], ifile), logact, 5):
2752
+ if iext == "zip":
2753
+ path = op.dirname(iname)
2754
+ if path and path != '.': move_local_file(iname, op.basename(iname), logact)
2755
+ if not keep: delete_local_file(ifile, logact)
2756
+
2757
+ if oname != iname: # move/copy
2758
+ path = op.dirname(oname)
2759
+ if path and not op.exists(path): make_local_directory(path, logact)
2760
+ if keep and not op.exists(kfile):
2761
+ local_copy_local(oname, iname, logact)
2762
+ kfile = iname
2763
+ else:
2764
+ move_local_file(oname, iname, logact)
2765
+
2766
+ if oext: # compress
2767
+ if keep and not op.exists(kfile):
2768
+ if oext == "zip":
2769
+ kfile = oname
2770
+ else:
2771
+ local_copy_local(kfile, oname, logact)
2772
+
2773
+ if oext == "zip":
2774
+ path = op.dirname(oname)
2775
+ if path:
2776
+ if path != '.': path = change_local_directory(path, logact)
2777
+ bname = op.basename(oname)
2778
+ PgLOG.pgsystem("{} {}.zip {}".format(PGCMPS[oext][0], bname, bname), logact, 5)
2779
+ if path != '.': change_local_directory(path, logact)
2780
+ else:
2781
+ PgLOG.pgsystem("{} {} {}".format(PGCMPS[oext][0], ofile, oname), logact, 5)
2782
+
2783
+ if not keep and op.exists(ofile): delete_local_file(oname, logact)
2784
+ else:
2785
+ PgLOG.pgsystem("{} {}".format(PGCMPS[oext][0], oname), logact, 5)
2786
+
2787
+ if keep and op.exists(kfile) and kfile != ifile:
2788
+ if op.exist(ifile):
2789
+ delete_local_file(kfile, logact)
2790
+ else:
2791
+ move_local_file(ifile, kfile, logact)
2792
+
2793
+ if op.exists(ofile):
2794
+ return PgLOG.SUCCESS
2795
+ else:
2796
+ return errlog("{}: ERROR convert from {}".format(ofile, ifile), 'L', 1, logact)
2797
+
2798
+ #
2799
+ # comapre two files from given two hash references to the file information
2800
+ # return 0 if same, 1 different, -1 if can not compare
2801
+ #
2802
+ def compare_file_info(ainfo, binfo):
2803
+
2804
+ if not (ainfo and binfo): return -1 # at least one is missing
2805
+
2806
+ return (0 if (ainfo['data_size'] == binfo['data_size'] and
2807
+ ainfo['date_modified'] == binfo['date_modified'] and
2808
+ ainfo['time_modified'] == binfo['time_modified']) else 1)
2809
+
2810
+ #
2811
+ # get local_dirname
2812
+ #
2813
+ def get_local_dirname(file):
2814
+
2815
+ dir = op.dirname(file)
2816
+ if dir == '.': dir = os.getcwd()
2817
+
2818
+ return dir
2819
+
2820
+ #
2821
+ # collect valid file names under a given directory, current directory if empty
2822
+ #
2823
+ def get_directory_files(dir = None, limit = 0, level = 0):
2824
+
2825
+ files = []
2826
+ if dir:
2827
+ if level == 0 and op.isfile(dir):
2828
+ files.append(dir)
2829
+ return files
2830
+ dir += "/*"
2831
+ else:
2832
+ dir = "*"
2833
+
2834
+ for file in glob.glob(dir):
2835
+ if op.isdir(file):
2836
+ if limit == 0 or (limit-level) > 0:
2837
+ fs = get_directory_files(file, limit, level+1)
2838
+ if fs: files.extend(fs)
2839
+ else:
2840
+ files.append(file)
2841
+
2842
+ return files if files else None
2843
+
2844
+ #
2845
+ # reads a local file into a string and returns it
2846
+ #
2847
+ def read_local_file(file, logact = 0):
2848
+
2849
+ try:
2850
+ fd = open(file, 'r')
2851
+ except Exception as e:
2852
+ return errlog("{}: {}".format(file, str(e)), 'L', 1, logact)
2853
+ else:
2854
+ fstr = fd.read()
2855
+ fd.close()
2856
+
2857
+ return fstr
2858
+
2859
+ #
2860
+ # open a local file and return the file handler
2861
+ #
2862
+ def open_local_file(file, mode = 'r', logact = PgLOG.LOGERR):
2863
+
2864
+ try:
2865
+ fd = open(file, mode)
2866
+ except Exception as e:
2867
+ return errlog("{}: {}".format(file, str(e)), 'L', 1, logact)
2868
+
2869
+ return fd
2870
+
2871
+ #
2872
+ # change absolute paths to relative paths
2873
+ #
2874
+ def get_relative_paths(files, cdir, logact = 0):
2875
+
2876
+ cnt = len(files)
2877
+ if cnt == 0: return files
2878
+ if not cdir: cdir = os.getcwd()
2879
+
2880
+ for i in range(cnt):
2881
+ afile = files[i]
2882
+ if op.isabs(afile):
2883
+ files[i] = PgLOG.join_paths(afile, cdir, 1)
2884
+ else:
2885
+ PgLOG.pglog("{}: is not under the working directory '{}'".format(afile, cdir), logact)
2886
+
2887
+ return files
2888
+
2889
+ #
2890
+ # check if the action to path is blocked
2891
+ #
2892
+ def check_block_path(path, act = '', logact = 0):
2893
+
2894
+ blockpath = PgLOG.PGLOG['USRHOME']
2895
+ if not act: act = 'Copy'
2896
+
2897
+ if re.match(r'^{}'.format(blockpath), path):
2898
+ return PgLOG.pglog("{}: {} to {} is blocked".format(path, act, blockpath), logact)
2899
+ else:
2900
+ return 1
2901
+
2902
+ #
2903
+ # join two filenames by uing the common prefix/suffix and keeping the different main bodies,
2904
+ # the bodies are seprated by sep replace fext with text if provided
2905
+ #
2906
+ def join_filenames(name1, name2, sep = '-', fext = None, text = None):
2907
+
2908
+ if fext:
2909
+ name1 = remove_file_extention(name1, fext)
2910
+ name2 = remove_file_extention(name2, fext)
2911
+
2912
+ if name1 == name2:
2913
+ fname = name1
2914
+ else:
2915
+ fname = suffix = ''
2916
+ cnt1 = len(name1)
2917
+ cnt2 = len(name2)
2918
+ cnt = (cnt1 if cnt1 < cnt2 else cnt2)
2919
+
2920
+ # get common prefix
2921
+ for pcnt in range(cnt):
2922
+ if name1[pcnt] != name2[pcnt]: break
2923
+
2924
+ # get common suffix
2925
+ cnt -= pcnt
2926
+ for scnt in range(0, cnt):
2927
+ if name1[cnt1-scnt-1] != name2[cnt2-scnt-1]: break
2928
+
2929
+ body1 = name1[pcnt:(cnt1-scnt)]
2930
+ body2 = name2[pcnt:(cnt2-scnt)]
2931
+ if scnt > 0:
2932
+ suffix = name2[(cnt1-scnt):cnt1]
2933
+ if name1[cnt1-scnt-1].isnumeric():
2934
+ ms = re.match(r'^([\d\.-]*\d)', suffix)
2935
+ if ms: body1 += ms.group(1) # include trailing digit chrs to body1
2936
+ if pcnt > 0:
2937
+ fname = name1[0:pcnt]
2938
+ if name2[pcnt].isnumeric():
2939
+ ms = re.search(r'(\d[\d\.-]*)$', fname)
2940
+ if ms: body2 = ms.group(1) + body2 # include leading digit chrs to body2
2941
+
2942
+ fname += body1 + sep + body2
2943
+ if suffix: fname += suffix
2944
+
2945
+ if text: fname += "." + text
2946
+
2947
+ return fname
2948
+
2949
+ # remove given file extention if provided
2950
+ # otherwise try to remove predfined compression extention in PGCMPS
2951
+ def remove_file_extention(fname, fext):
2952
+
2953
+ if not fname: return ''
2954
+
2955
+ if fext:
2956
+ fname = re.sub(r'\.{}$'.format(fext), '', fname, 1, re.I)
2957
+ else:
2958
+ for fext in PGCMPS:
2959
+ mp = r'\.{}$'.format(fext)
2960
+ if re.search(mp, fname):
2961
+ fname = re.sub(mp, '', fname, 1, re.I)
2962
+ break
2963
+
2964
+ return fname
2965
+
2966
+ # check if a previous down storage system is up now for given dflag
2967
+ #
2968
+ # return error message if failed checking, and None otherwise
2969
+ #
2970
+ def check_storage_down(dflag, dpath, dscheck, logact = 0):
2971
+
2972
+ if dflag not in DHOSTS:
2973
+ if logact: PgLOG.pglog(dflag + ": Unknown Down Flag for Storage Systems", logact)
2974
+ return None
2975
+ dhost = DHOSTS[dflag]
2976
+ if not dpath and dflag in DPATHS: dpath = DPATHS[dflag]
2977
+ for loop in range(2):
2978
+ (stat, msg) = host_down_status(dpath, dhost, 1, logact)
2979
+ if stat < 0: break # stop retry for planned down
2980
+
2981
+ if not dscheck and PgLOG.PGLOG['DSCHECK']: dscheck = PgLOG.PGLOG['DSCHECK']
2982
+ if dscheck:
2983
+ didx = dscheck['dflags'].find(dflag)
2984
+ if msg:
2985
+ if didx < 0: dscheck['dflags'] += dflag
2986
+ else:
2987
+ if didx > -1: dscheck['dflags'].replace(dflag, '', 1)
2988
+
2989
+ return msg
2990
+
2991
+ #
2992
+ # check if previous down storage systems recorded in the dflags
2993
+ #
2994
+ # return an array of strings for storage systems that are still down,
2995
+ # and empty array if all up
2996
+ #
2997
+ def check_storage_dflags(dflags, dscheck = None, logact = 0):
2998
+
2999
+ if not dflags: return 0
3000
+
3001
+ isdict = isinstance(dflags, dict)
3002
+ msgary = []
3003
+ for dflag in dflags:
3004
+ msg = check_storage_down(dflag, dflags[dflag] if isdict else None, dscheck, logact)
3005
+ if msg: msgary.append(msg)
3006
+
3007
+ if not msgary:
3008
+ if not dscheck and PgLOG.PGLOG['DSCHECK']: dscheck = PgLOG.PGLOG['DSCHECK']
3009
+ cidx = dscheck['cindex'] if dscheck else 0
3010
+ # clean dflags if the down storage systems are all up
3011
+ if cidx: PgDBI.pgexec("UPDATE dscheck SET dflags = '' WHERE cindex = {}".format(cidx), logact)
3012
+
3013
+ return msgary