rda-python-common 2.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.
@@ -0,0 +1,1689 @@
1
+ #
2
+ ###############################################################################
3
+ #
4
+ # Title : PgLOG.py -- Module for logging messages.
5
+ # Author : Zaihua Ji, zji@ucar.edu
6
+ # Date : 03/02/2016
7
+ # 2025-01-10 transferred to package rda_python_common from
8
+ # https://github.com/NCAR/rda-shared-libraries.git
9
+ # Purpose : Python library module to log message and also do other things
10
+ # according to the value of logact, like display the error
11
+ # message on screen and exit script
12
+ #
13
+ # Github : https://github.com/NCAR/rda-python-common.git
14
+ #
15
+ ###############################################################################
16
+
17
+ import sys
18
+ import os
19
+ import re
20
+ import pwd
21
+ import grp
22
+ import shlex
23
+ import smtplib
24
+ from email.message import EmailMessage
25
+ from subprocess import Popen, PIPE
26
+ from os import path as op
27
+ import time
28
+ import socket
29
+ import shutil
30
+ import traceback
31
+ from unidecode import unidecode
32
+
33
+ # define some constants for logging actions
34
+ MSGLOG = (0x00001) # logging message
35
+ WARNLG = (0x00002) # show logging message as warning
36
+ EXITLG = (0x00004) # exit after logging
37
+ LOGWRN = (0x00003) # MSGLOG|WARNLG
38
+ LOGEXT = (0x00005) # MSGLOG|EXITLG
39
+ WRNEXT = (0x00006) # WARNLG|EXITLG
40
+ LGWNEX = (0x00007) # MSGLOG|WARNLG|EXITLG
41
+ EMLLOG = (0x00008) # append message to email buffer
42
+ LGWNEM = (0x0000B) # MSGLOG|WARNLG|EMLLOG
43
+ LWEMEX = (0x0000F) # MSGLOG|WARNLG|EMLLOG|EXITLG
44
+ ERRLOG = (0x00010) # error log only, output to STDERR
45
+ LOGERR = (0x00011) # MSGLOG|ERRLOG
46
+ LGEREX = (0x00015) # MSGLOG|ERRLOG|EXITLG
47
+ LGEREM = (0x00019) # MSGLOG|ERRLOG|EMLLOG
48
+ DOLOCK = (0x00020) # action to lock table record(s)
49
+ ENDLCK = (0x00040) # action to end locking table record(s)
50
+ AUTOID = (0x00080) # action to retrieve the last auto added id
51
+ DODFLT = (0x00100) # action to set empty values to default ones
52
+ SNDEML = (0x00200) # action to send email now
53
+ RETMSG = (0x00400) # action to return the message back
54
+ FRCLOG = (0x00800) # force logging message
55
+ SEPLIN = (0x01000) # add a separating line for email/STDOUT/STDERR
56
+ BRKLIN = (0x02000) # add a line break for email/STDOUT/STDERR
57
+ EMLTOP = (0x04000) # prepend message to email buffer
58
+ RCDMSG = (0x00814) # make sure to record logging message
59
+ MISLOG = (0x00811) # cannot access logfile
60
+ EMLSUM = (0x08000) # record as email summary
61
+ EMEROL = (0x10000) # record error as email only
62
+ EMLALL = (0x1D208) # all email acts
63
+ DOSUDO = (0x20000) # add 'sudo -u PGLOG['GDEXUSER']'
64
+ NOTLOG = (0x40000) # do not log any thing
65
+ OVRIDE = (0x80000) # do override existing file or record
66
+ NOWAIT = (0x100000) # do not wait on globus task to finish
67
+ ADDTBL = (0x200000) # action to add a new table if it does not exist
68
+ SKPTRC = (0x400000) # action to skip tracing when log errors
69
+ UCNAME = (0x800000) # action to change query field names to upper case
70
+ UCLWEX = (0x800015) # UCNAME|MSGLOG|WARNLG|EXITLG
71
+ PFSIZE = (0x1000000) # total file size under a path
72
+
73
+ SUCCESS = 1 # Successful function call
74
+ FINISH = 2 # go through a function, including time out
75
+ FAILURE = 0 # Unsuccessful function call
76
+
77
+ PGLOG = { # more defined in untaint_suid() with environment variables
78
+ 'EMLADDR' : '',
79
+ 'CCDADDR' : '',
80
+ 'SEPLINE' : "===========================================================\n",
81
+ 'TWOGBS' : 2147483648,
82
+ 'ONEGBS' : 1073741824,
83
+ 'MINSIZE' : 100, # minimal file size in bytes to be valid
84
+ 'LOGMASK' : (0xFFFFFF), # log mask to turn off certain log action bits
85
+ 'BCKGRND' : 0, # background process flag -b
86
+ 'ERRCNT' : 0, # record number of errors for email
87
+ 'ERRMSG' : '', # record error message for email
88
+ 'SUMMSG' : '', # record summary message for email
89
+ 'EMLMSG' : '', # record detail message for email
90
+ 'PRGMSG' : '', # record progressing message for email, replaced each time
91
+ 'GMTZ' : 0, # 0 - use local time, 1 - use greenwich mean time
92
+ 'NOLEAP' : 0, # 1 - skip 29 of Feburary while add days to date
93
+ 'GMTDIFF' : 6, # gmt is 6 hours ahead of us
94
+ 'CURUID' : None, # the login name who executes the program
95
+ 'SETUID' : '', # the login name for suid if it is different to the CURUID
96
+ 'FILEMODE': 0o664, # default 8-base file mode
97
+ 'EXECMODE': 0o775, # default 8-base executable file mode or directory mode
98
+ 'ARCHHOST': "hpss", # change to hpss from mss
99
+ 'ARCHROOT': "/FS/DECS", # root path for segregated tape on hpss
100
+ 'BACKROOT': "/DRDATA/DECS", # backup path for desaster recovering tape on hpss
101
+ 'OLDAROOT': "/FS/DSS", # old root path on hpss
102
+ 'OLDBROOT': "/DRDATA/DSS", # old backup tape on hpss
103
+ 'GDEXUSER' : "gdexdata", # common gdex user name
104
+ 'GDEXEMAIL' : "zji", # specialist to receipt email intead of common gdex user name
105
+ 'SUDOGDEX' : 0, # 1 to allow sudo to PGLOG['GDEXUSER']
106
+ 'HOSTNAME' : '', # current host name the process in running on
107
+ 'OBJCTSTR' : "object",
108
+ 'BACKUPNM' : "quasar",
109
+ 'DRDATANM' : "drdata",
110
+ 'GPFSNAME' : "glade",
111
+ 'SLMNAME' : "SLURM",
112
+ 'PBSNAME' : "PBS",
113
+ 'DSIDCHRS' : "d",
114
+ 'DOSHELL' : False,
115
+ 'NEWDSID' : True,
116
+ 'PUSGDIR' : None,
117
+ 'BCHHOSTS' : "PBS",
118
+ 'HOSTTYPE' : 'dav', # default HOSTTYPE
119
+ 'EMLMAX' : 256, # up limit of email line count
120
+ 'PGBATCH' : '', # current batch service name, SLURM or PBS
121
+ 'PGBINDIR' : '',
122
+ 'SLMTIME' : 604800, # max runtime for SLURM bath job, (7x24x60x60 seconds)
123
+ 'PBSTIME' : 86400, # max runtime for PBS bath job, (24x60x60 seconds)
124
+ 'MSSGRP' : None, # set if set to different HPSS group
125
+ 'GDEXGRP' : "decs",
126
+ 'EMLSEND' : None, # path to sendmail, None if not exists
127
+ 'DSCHECK' : None, # carry some cached dscheck information
128
+ 'PGDBBUF' : None, # reference to a connected database object
129
+ 'HPSSLMT' : 10, # up limit of HPSS streams
130
+ 'NOQUIT' : 0, # do not quit if this flag is set for daemons
131
+ 'DBRETRY' : 2, # db retry count after error
132
+ 'TIMEOUT' : 15, # default timeout (in seconds) for tosystem()
133
+ 'CMDTIME' : 120, # default command time (in seconds) for pgsystem() to record end time
134
+ 'SYSERR' : None, # cache the error message generated inside pgsystem()
135
+ 'ERR2STD' : [], # if non-empty reference to array of strings, change stderr to stdout if match
136
+ 'STD2ERR' : [], # if non-empty reference to array of strings, change stdout to stderr if match
137
+ 'MISSFILE': "No such file or directory",
138
+ 'GITHUB' : "https://github.com" , # github server
139
+ 'EMLSRVR' : "ndir.ucar.edu", # UCAR email server and port
140
+ 'EMLPORT' : 25
141
+ }
142
+
143
+ PGLOG['RDAUSER'] = PGLOG['GDEXUSER']
144
+ PGLOG['RDAGRP'] = PGLOG['GDEXGRP']
145
+ PGLOG['RDAEMAIL'] = PGLOG['GDEXEMAIL']
146
+ PGLOG['SUDORDA'] = PGLOG['SUDOGDEX']
147
+
148
+ HOSTTYPES = {
149
+ 'rda' : 'dsg_mach',
150
+ 'casper' : 'dav',
151
+ 'crhtc' : 'dav',
152
+ 'cron' : 'dav',
153
+ 'cheyenne' : 'ch',
154
+ 'chadmin' : 'ch'
155
+ }
156
+
157
+ CPID = {
158
+ 'PID' : "",
159
+ 'CTM' : int(time.time()),
160
+ 'CMD' : "",
161
+ 'CPID' : "",
162
+ }
163
+
164
+ BCHCMDS = {'PBS' : 'qsub', 'SLURM' : 'sbatch'}
165
+
166
+ # global dists to cashe information
167
+ COMMANDS = {}
168
+ SLMHOSTS = []
169
+ SLMSTATS = {}
170
+ PBSHOSTS = []
171
+ PBSSTATS = {}
172
+
173
+ #
174
+ # get time string in format YYMMDDHHNNSS for given ctime; or current time if ctime is 0
175
+ #
176
+ def current_datetime(ctime = 0):
177
+
178
+ if PGLOG['GMTZ']:
179
+ dt = time.gmtime(ctime) if ctime else time.gmtime()
180
+ else:
181
+ dt = time.localtime(ctime) if ctime else time.localtime()
182
+
183
+ return "{:02}{:02}{:02}{:02}{:02}{:02}".format(dt[0], dt[1], dt[2], dt[3], dt[4], dt[5])
184
+
185
+ #
186
+ # get an environment variable and untaint it
187
+ #
188
+ def get_environment(name, default = None, logact = 0):
189
+
190
+ env = os.getenv(name, default)
191
+ if env is None and logact:
192
+ pglog(name + ": Environment variable is not defined", logact)
193
+
194
+ return env
195
+
196
+ #
197
+ # cache the msg string to global email entries for later call of send_email()
198
+ #
199
+ def set_email(msg, logact = 0):
200
+
201
+ if logact and msg:
202
+ if logact&EMLTOP:
203
+ if PGLOG['PRGMSG']:
204
+ msg = PGLOG['PRGMSG'] + "\n" + msg
205
+ PGLOG['PRGMSG'] = ""
206
+ if PGLOG['ERRCNT'] == 0:
207
+ if not re.search(r'\n$', msg): msg += "!\n"
208
+ else:
209
+ if PGLOG['ERRCNT'] == 1:
210
+ msg += " with 1 Error:\n"
211
+ else:
212
+ msg += " with {} Errors:\n".format(PGLOG['ERRCNT'])
213
+ msg += break_long_string(PGLOG['ERRMSG'], 512, None, PGLOG['EMLMAX']/2, None, 50, 25)
214
+ PGLOG['ERRCNT'] = 0
215
+ PGLOG['ERRMSG'] = ''
216
+
217
+ if PGLOG['SUMMSG']:
218
+ msg += PGLOG['SEPLINE']
219
+ if PGLOG['SUMMSG']: msg += "Summary:\n"
220
+ msg += break_long_string(PGLOG['SUMMSG'], 512, None, PGLOG['EMLMAX']/2, None, 50, 25)
221
+
222
+ if PGLOG['EMLMSG']:
223
+ msg += PGLOG['SEPLINE']
224
+ if PGLOG['SUMMSG']: msg += "Detail Information:\n"
225
+
226
+ PGLOG['EMLMSG'] = msg + break_long_string(PGLOG['EMLMSG'], 512, None, PGLOG['EMLMAX'], None, 50, 40)
227
+ PGLOG['SUMMSG'] = "" # in case not
228
+ else:
229
+ if logact&ERRLOG: # record error for email summary
230
+ PGLOG['ERRCNT'] += 1
231
+ if logact&BRKLIN: PGLOG['ERRMSG'] += "\n"
232
+ PGLOG['ERRMSG'] += "{}. {}".format(PGLOG['ERRCNT'], msg)
233
+ elif logact&EMLSUM:
234
+ if PGLOG['SUMMSG']:
235
+ if logact&BRKLIN: PGLOG['SUMMSG'] += "\n"
236
+ if logact&SEPLIN: PGLOG['SUMMSG'] += PGLOG['SEPLINE']
237
+ PGLOG['SUMMSG'] += msg # append
238
+
239
+ if logact&EMLLOG:
240
+ if PGLOG['EMLMSG']:
241
+ if logact&BRKLIN: PGLOG['EMLMSG'] += "\n"
242
+ if logact&SEPLIN: PGLOG['EMLMSG'] += PGLOG['SEPLINE']
243
+ PGLOG['EMLMSG'] += msg # append
244
+ elif msg is None:
245
+ PGLOG['EMLMSG'] = ""
246
+
247
+ #
248
+ # retrieve the cached email message
249
+ #
250
+ def get_email():
251
+ return PGLOG['EMLMSG']
252
+
253
+ #
254
+ # send a customized email with all entries included
255
+ #
256
+ def send_customized_email(logmsg, emlmsg, logact = LOGWRN):
257
+
258
+ entries = {
259
+ 'fr' : ["From", 1, None],
260
+ 'to' : ["To", 1, None],
261
+ 'cc' : ["Cc", 0, ''],
262
+ 'sb' : ["Subject", 1, None]
263
+ }
264
+
265
+ if logmsg:
266
+ logmsg += ': '
267
+ else:
268
+ logmsg = ''
269
+ msg = emlmsg
270
+ for ekey in entries:
271
+ entry = entries[ekey][0]
272
+ ms = re.search(r'(^|\n)({}: *(.*)\n)'.format(entry), emlmsg, re.I)
273
+ if ms:
274
+ vals = ms.groups()
275
+ msg = msg.replace(vals[1], '')
276
+ if vals[2]: entries[ekey][2] = vals[2]
277
+ elif entries[ekey][1]:
278
+ return pglog("{}Missing Entry '{}' for sending email".format(logmsg, entry), logact|ERRLOG)
279
+
280
+ ret = send_python_email(entries['sb'][2], entries['to'][2], msg, entries['fr'][2], entries['cc'][2], logact)
281
+ if ret == SUCCESS or not PGLOG['EMLSEND']: return ret
282
+
283
+ # try commandline sendmail
284
+ ret = pgsystem(PGLOG['EMLSEND'], logact, 4, emlmsg)
285
+ logmsg += "Email " + entries['to'][2]
286
+ if entries['cc'][2]: logmsg += " Cc'd " + entries['cc'][2]
287
+ logmsg += " Subject: " + entries['sb'][2]
288
+ if ret:
289
+ log_email(emlmsg)
290
+ pglog(logmsg, logact&(~EXITLG))
291
+ else:
292
+ errmsg = "Error sending email: " + logmsg
293
+ pglog(errmsg, (logact|ERRLOG)&~EXITLG)
294
+
295
+ return ret
296
+
297
+ #
298
+ # send an email; if empty msg send email message saved in PGLOG['EMLMSG'] instead
299
+ #
300
+ def send_email(subject = None, receiver = None, msg = None, sender = None, logact = LOGWRN):
301
+
302
+ return send_python_email(subject, receiver, msg, sender, None, logact)
303
+
304
+ #
305
+ # send an email via python module smtplib; if empty msg send email message saved
306
+ # in PGLOG['EMLMSG'] instead. pass cc = '' for skipping 'Cc: '
307
+ #
308
+ def send_python_email(subject = None, receiver = None, msg = None, sender = None, cc = None, logact = LOGWRN):
309
+
310
+ if not msg:
311
+ if PGLOG['EMLMSG']:
312
+ msg = PGLOG['EMLMSG']
313
+ PGLOG['EMLMSG'] = ''
314
+ else:
315
+ return ''
316
+
317
+ docc = False if cc else True
318
+ if not sender:
319
+ sender = PGLOG['CURUID']
320
+ if sender != PGLOG['GDEXUSER']: docc = False
321
+ if sender == PGLOG['GDEXUSER']: sender = PGLOG['GDEXEMAIL']
322
+ if sender.find('@') == -1: sender += "@ucar.edu"
323
+ if not receiver:
324
+ receiver = PGLOG['EMLADDR'] if PGLOG['EMLADDR'] else PGLOG['CURUID']
325
+ if receiver == PGLOG['GDEXUSER']: receiver = PGLOG['GDEXEMAIL']
326
+ if receiver.find('@') == -1: receiver += "@ucar.edu"
327
+
328
+ if docc and not re.match(PGLOG['GDEXUSER'], sender): add_carbon_copy(sender, 1)
329
+ emlmsg = EmailMessage()
330
+ emlmsg.set_content(msg)
331
+ emlmsg['From'] = sender
332
+ emlmsg['To'] = receiver
333
+ logmsg = "Email " + receiver
334
+ if cc == None: cc = PGLOG['CCDADDR']
335
+ if cc:
336
+ emlmsg['Cc'] = cc
337
+ logmsg += " Cc'd " + cc
338
+ if not subject: subject = "Message from {}-{}".format(PGLOG['HOSTNAME'], get_command())
339
+ # if not re.search(r'!$', subject): subject += '!'
340
+ emlmsg['Subject'] = subject
341
+ if CPID['CPID']: logmsg += " in " + CPID['CPID']
342
+ logmsg += ", Subject: {}\n".format(subject)
343
+ try:
344
+ eml = smtplib.SMTP(PGLOG['EMLSRVR'], PGLOG['EMLPORT'])
345
+ eml.send_message(emlmsg)
346
+ except smtplib.SMTPException as err:
347
+ errmsg = f"Error sending email:\n{err}\n{logmsg}"
348
+ return pglog(errmsg, (logact|ERRLOG)&~EXITLG)
349
+ finally:
350
+ eml.quit()
351
+ log_email(str(emlmsg))
352
+ pglog(logmsg, logact&~EXITLG)
353
+ return SUCCESS
354
+
355
+ #
356
+ # log email sent
357
+ #
358
+ def log_email(emlmsg):
359
+
360
+ if not CPID['PID']: CPID['PID'] = "{}-{}-{}".format(PGLOG['HOSTNAME'], get_command(), PGLOG['CURUID'])
361
+ cmdstr = "{} {} at {}\n".format(CPID['PID'], break_long_string(CPID['CMD'], 40, "...", 1), current_datetime())
362
+ fn = "{}/{}".format(PGLOG['LOGPATH'], PGLOG['EMLFILE'])
363
+ try:
364
+ f = open(fn, 'a')
365
+ f.write(cmdstr + emlmsg)
366
+ f.close()
367
+ except FileNotFoundError as e:
368
+ print(e)
369
+
370
+ #
371
+ # Function: cmdlog(cmdline)
372
+ # cmdline - program name and all arguments
373
+ # ctime - time (in seconds) when the command starts
374
+ #
375
+ def cmdlog(cmdline = None, ctime = 0, logact = None):
376
+
377
+ if logact is None: logact = MSGLOG|FRCLOG
378
+ if not ctime: ctime = int(time.time())
379
+
380
+ if not cmdline or re.match('(end|quit|exit|abort)', cmdline, re.I):
381
+ cmdline = cmdline.capitalize() if cmdline else "Ends"
382
+ cinfo = cmd_execute_time("{} {}".format(CPID['PID'], cmdline), (ctime - CPID['CTM'])) + ": "
383
+ if CPID['CPID']: cinfo += CPID['CPID'] + " <= "
384
+ cinfo += break_long_string(CPID['CMD'], 40, "...", 1)
385
+ if logact: pglog(cinfo, logact)
386
+ else:
387
+ cinfo = current_datetime(ctime)
388
+ if re.match(r'CPID \d+', cmdline):
389
+ CPID['PID'] = "{}({})-{}{}".format(PGLOG['HOSTNAME'], os.getpid(), PGLOG['CURUID'], cinfo)
390
+ if logact: pglog("{}: {}".format(CPID['PID'], cmdline), logact)
391
+ CPID['CPID'] = cmdline
392
+ elif CPID['PID'] and re.match(r'(starts|catches) ', cmdline):
393
+ if logact: pglog("{}: {} at {}".format(CPID['PID'], cmdline, cinfo), logact)
394
+ else:
395
+ CPID['PID'] = "{}({})-{}{}".format(PGLOG['HOSTNAME'], os.getpid(), PGLOG['CURUID'], cinfo)
396
+ if logact: pglog("{}: {}".format(CPID['PID'], cmdline), logact)
397
+ CPID['CMD'] = cmdline
398
+
399
+ CPID['CTM'] = ctime
400
+
401
+ #
402
+ # Function: pglog(msg, logact) return FAILURE or log message if not exit
403
+ # msg -- message to log
404
+ # locact -- logging actions: MSGLOG, WARNLG, ERRLOG, EXITLG, EMLLOG, & SNDEML
405
+ #
406
+ # log and display message/error and exit program according logact value
407
+ #
408
+ def pglog(msg, logact = MSGLOG):
409
+
410
+ retmsg = None
411
+ logact &= PGLOG['LOGMASK'] # filtering the log actions
412
+ if logact&RCDMSG: logact |= MSGLOG
413
+ if PGLOG['NOQUIT']: logact &= ~EXITLG
414
+ if logact&EMEROL:
415
+ if logact&EMLLOG: logact &= ~EMLLOG
416
+ if not logact&ERRLOG: logact &= ~EMEROL
417
+
418
+ msg = msg.lstrip() if msg else '' # remove leading whitespaces for logging message
419
+ if logact&EXITLG:
420
+ ext = "Exit 1 in {}\n".format(os.getcwd())
421
+ if msg: msg = msg.rstrip() + "; "
422
+ msg += ext
423
+ else:
424
+ if msg and not re.search(r'(\n|\r)$', msg): msg += "\n"
425
+ if logact&RETMSG: retmsg = msg
426
+
427
+ if logact&EMLALL:
428
+ if logact&SNDEML or not msg:
429
+ title = (msg if msg else "Message from {}-{}".format(PGLOG['HOSTNAME'], get_command()))
430
+ msg = title
431
+ send_email(title.rstrip())
432
+ elif msg:
433
+ set_email(msg, logact)
434
+
435
+ if not msg: return (retmsg if retmsg else FAILURE)
436
+
437
+ if logact&EXITLG and (PGLOG['EMLMSG'] or PGLOG['SUMMSG'] or PGLOG['ERRMSG'] or PGLOG['PRGMSG']):
438
+ if not logact&EMLALL: set_email(msg, logact)
439
+ title = "ABORTS {}-{}".format(PGLOG['HOSTNAME'], get_command())
440
+ set_email((("ABORTS " + CPID['PID']) if CPID['PID'] else title), EMLTOP)
441
+ msg = title + '\n' + msg
442
+ send_email(title)
443
+
444
+ if logact&LOGERR: # make sure error is always logged
445
+ msg = break_long_string(msg)
446
+ if logact&(ERRLOG|EXITLG):
447
+ cmdstr = get_error_command(int(time.time()), logact)
448
+ msg = cmdstr + msg
449
+
450
+ if not logact&NOTLOG:
451
+ if logact&ERRLOG:
452
+ if not PGLOG['ERRFILE']: PGLOG['ERRFILE'] = re.sub(r'.log$', '.err', PGLOG['LOGFILE'])
453
+ write_message(msg, f"{PGLOG['LOGPATH']}/{PGLOG['ERRFILE']}", logact)
454
+ if logact&EXITLG:
455
+ write_message(cmdstr, f"{PGLOG['LOGPATH']}/{PGLOG['LOGFILE']}", logact)
456
+ else:
457
+ write_message(msg, f"{PGLOG['LOGPATH']}/{PGLOG['LOGFILE']}", logact)
458
+
459
+ if not PGLOG['BCKGRND'] and logact&(ERRLOG|WARNLG):
460
+ write_message(msg, None, logact)
461
+
462
+ if logact&EXITLG:
463
+ pgexit(1)
464
+ else:
465
+ return (retmsg if retmsg else FAILURE)
466
+
467
+ #
468
+ # write a log message
469
+ #
470
+ def write_message(msg, file, logact):
471
+
472
+ doclose = False
473
+ errlog = logact&ERRLOG
474
+ if file:
475
+ try:
476
+ OUT = open(file, 'a')
477
+ doclose = True
478
+ except FileNotFoundError:
479
+ OUT = sys.stderr if logact&(ERRLOG|EXITLG) else sys.stdout
480
+ OUT.write(f"Log File not found: {file}")
481
+ else:
482
+ OUT = sys.stderr if logact&(ERRLOG|EXITLG) else sys.stdout
483
+ if logact&BRKLIN: OUT.write("\n")
484
+ if logact&SEPLIN: OUT.write(PGLOG['SEPLINE'])
485
+ OUT.write(msg)
486
+ if errlog and file and not logact&(EMLALL|SKPTRC): OUT.write(get_call_trace())
487
+ if doclose: OUT.close()
488
+
489
+ #
490
+ # check and disconnet database before exit
491
+ #
492
+ def pgexit(stat = 0):
493
+
494
+ if PGLOG['PGDBBUF']: PGLOG['PGDBBUF'].close()
495
+ sys.exit(stat)
496
+
497
+ #
498
+ # get a command string for error log dump
499
+ #
500
+ def get_error_command(ctime, logact):
501
+
502
+ if not CPID['PID']: CPID['PID'] = "{}-{}-{}".format(PGLOG['HOSTNAME'], get_command(), PGLOG['CURUID'])
503
+ cmdstr = "{} {}".format((("ABORTS" if logact&ERRLOG else "QUITS") if logact&EXITLG else "ERROR"), CPID['PID'])
504
+ cmdstr = cmd_execute_time(cmdstr, (ctime - CPID['CTM']))
505
+ if CPID['CPID']: cmdstr += " {} <=".format(CPID['CPID'])
506
+ cmdstr += " {} at {}\n".format(break_long_string(CPID['CMD'], 40, "...", 1), current_datetime(ctime))
507
+
508
+ return cmdstr
509
+
510
+ #
511
+ # get call trace track
512
+ #
513
+ def get_call_trace(cut = 1):
514
+
515
+ t = traceback.extract_stack()
516
+ n = len(t) - cut
517
+ str = ''
518
+ sep = 'Trace: '
519
+ for i in range(n):
520
+ tc = t[i]
521
+ str += "{}{}({}){}".format(sep, tc[0], tc[1], ("" if tc[2] == '<module>' else "{%s()}" % tc[2]))
522
+ if i == 0: sep = '=>'
523
+
524
+ return str + "\n" if str else ""
525
+
526
+ #
527
+ # get caller file name
528
+ #
529
+ def get_caller_file(cidx = 0):
530
+
531
+ return traceback.extract_stack()[cidx][0]
532
+
533
+ #
534
+ # log message, msg, for degugging processes according to the debug level
535
+ #
536
+ def pgdbg(level, msg = None, do_trace = True):
537
+
538
+ if not PGLOG['DBGLEVEL']: return # no further action
539
+
540
+ if not isinstance(level, int):
541
+ ms = re.match(r'^(\d+)', level)
542
+ level = int(ms.group(1)) if ms else 0
543
+
544
+ levels = [0, 0]
545
+ if isinstance(PGLOG['DBGLEVEL'], int):
546
+ levels[1] = PGLOG['DBGLEVEL']
547
+ else:
548
+ ms = re.match(r'^(\d+)$', PGLOG['DBGLEVEL'])
549
+ if ms:
550
+ levels[1] = int(ms.group(1))
551
+ else:
552
+ ms = re.match(r'(\d*)-(\d*)', PGLOG['DBGLEVEL'])
553
+ if ms:
554
+ levels[0] = int(ms.group(1)) if ms.group(1) else 0
555
+ levels[1] = int(ms.group(2)) if ms.group(2) else 9999
556
+
557
+ if level > levels[1] or level < levels[0]: return # debug level is out of range
558
+
559
+ if 'DBGPATH' in PGLOG:
560
+ dfile = PGLOG['DBGPATH'] + '/' + PGLOG['DBGFILE']
561
+ else:
562
+ dfile = PGLOG['DBGFILE']
563
+ if not msg:
564
+ pglog("Append debug Info (levels {}-{}) to {}".format(levels[0], levels[1], dfile), WARNLG)
565
+ msg = "DEBUG for " + CPID['PID'] + " "
566
+ if CPID['CPID']: msg += CPID['CPID'] + " <= "
567
+ msg += break_long_string(CPID['CMD'], 40, "...", 1)
568
+
569
+ # logging debug info
570
+ DBG = open(dfile, 'a')
571
+ DBG.write("{}:{}\n".format(level, msg))
572
+ if do_trace: DBG.write(get_call_trace())
573
+ DBG.close()
574
+
575
+ #
576
+ # return trimed string (strip leading and trailling spaces); remove comments led by '#' if rmcmt > 0
577
+ #
578
+ def pgtrim(line, rmcmt = 1):
579
+
580
+ if line:
581
+ if rmcmt:
582
+ if re.match(r'^\s*#', line): # comment line
583
+ line = ''
584
+ elif rmcmt > 1:
585
+ ms = re.search(r'^(.+)\s\s+\#', line)
586
+ if ms: line = ms.group(1) # remove comment and its leading whitespaces
587
+ else:
588
+ ms = re.search(r'^(.+)\s+\#', line)
589
+ if ms: line = ms.group(1) # remove comment and its leading whitespace
590
+
591
+ line = line.strip() # remove leading and trailing whitespaces
592
+
593
+ return line
594
+
595
+ #
596
+ # set PGLOG['PUSGDIR'] from the program file with full path
597
+ #
598
+ def set_help_path(progfile):
599
+
600
+ PGLOG['PUSGDIR'] = op.dirname(op.abspath(progfile))
601
+
602
+ #
603
+ # Function: show_usage(progname: Perl program name to get file "progname.usg")
604
+ #
605
+ # show program usage in file "PGLOG['PUSGDIR']/progname.usg" on screen with unix
606
+ # system function 'pg', exit program when done.
607
+ #
608
+ def show_usage(progname, opts = None):
609
+
610
+ if PGLOG['PUSGDIR'] is None: set_help_path(get_caller_file(1))
611
+ usgname = join_paths(PGLOG['PUSGDIR'], progname + '.usg')
612
+
613
+ if opts:
614
+ # show usage for individual option of dsarch
615
+ for opt in opts:
616
+ if opts[opt][0] == 0:
617
+ msg = "Mode"
618
+ elif opts[opt][0] == 1:
619
+ msg = "Single-Value Information"
620
+ elif opts[opt][0] == 2:
621
+ msg = "Multi-Value Information"
622
+ else:
623
+ msg = "Action"
624
+
625
+ sys.stdout.write("\nDescription of {} Option -{}:\n".format(msg, opt))
626
+ IN = open(usgname, 'r')
627
+ nilcnt = begin = 0
628
+ for line in IN:
629
+ if begin == 0:
630
+ rx = " -{} or -".format(opt)
631
+ if re.match(rx, line): begin = 1
632
+ elif re.match(r'^\s*$', line):
633
+ if nilcnt: break
634
+ nilcnt = 1
635
+ else:
636
+ if re.match(r'\d[\.\s\d]', line): break # section title
637
+ if nilcnt and re.match(r' -\w\w or -', line): break
638
+ nilcnt = 0
639
+ if begin: sys.stdout.write(line)
640
+ IN.close()
641
+ else:
642
+ os.system("more " + usgname)
643
+
644
+ pgexit(0)
645
+
646
+ #
647
+ # compare error message to patterns saved in PGLOG['ERR2STD']
648
+ # return 1 if matched; 0 otherwise
649
+ #
650
+ def err2std(line):
651
+
652
+ for err in PGLOG['ERR2STD']:
653
+ if line.find(err) > -1: return 1
654
+ return 0
655
+
656
+ #
657
+ # compare message to patterns saved in PGLOG['STD2ERR']
658
+ # return 1 if matched; 0 otherwise
659
+ #
660
+ def std2err(line):
661
+
662
+ for out in PGLOG['STD2ERR']:
663
+ if line.find(out) > -1: return 1
664
+ return 0
665
+
666
+ #
667
+ # Function: pgsystem(pgcmd, logact, cmdopt, instr)
668
+ # pgcmd - Linux system command, can be a string, "ls -l", or a list, ['ls', '-l']
669
+ # logact - logging action option, defaults to PgLOG.LOGWRN
670
+ # cmdopt - command control option, default to 5 (1+4)
671
+ # 0 - no command control,
672
+ # 1 - log pgcmd (include the sub command calls),
673
+ # 2 - log standard output,
674
+ # 4 - log error output
675
+ # 7 - log all (pgcmd, and standard/error outputs),
676
+ # 8 - log command with time,
677
+ # 16 - return standard output message upon success
678
+ # 32 - log error as standard output
679
+ # 64 - force returning FAILURE if called process aborts
680
+ # 128 - tries 2 times for failed command before quits
681
+ # 256 - cache standard error message
682
+ # 512 - log instr & seconds with pgcmd if cmdopt&1
683
+ # 1024 - turn on shell
684
+ # instr - input string passing to the command via stdin if not None
685
+ # seconds - number of seconds to wait for a timeout process if > 0
686
+ #
687
+ def pgsystem(pgcmd, logact = LOGWRN, cmdopt = 5, instr = None, seconds = 0):
688
+
689
+ ret = SUCCESS
690
+ if not pgcmd: return ret # empty command
691
+
692
+ act = logact&~EXITLG
693
+ if act&ERRLOG:
694
+ act &= ~ERRLOG
695
+ act |= WARNLG
696
+
697
+ if act&MSGLOG: act |= FRCLOG # make sure system calls always logged
698
+ cmdact = act if cmdopt&1 else 0
699
+ doshell = True if cmdopt&1024 else PGLOG['DOSHELL']
700
+
701
+ if isinstance(pgcmd, str):
702
+ cmdstr = pgcmd
703
+ if not doshell and re.search(r'[*?<>|;]', pgcmd): doshell = True
704
+ execmd = pgcmd if doshell else shlex.split(pgcmd)
705
+ else:
706
+ cmdstr = shlex.join(pgcmd)
707
+ execmd = cmdstr if doshell else pgcmd
708
+
709
+ if cmdact:
710
+ if cmdopt&8:
711
+ cmdlog("starts '{}'".format(cmdstr), None, cmdact)
712
+ else:
713
+ pglog("> " + cmdstr, cmdact)
714
+ if cmdopt&512 and (instr or seconds):
715
+ msg = ''
716
+ if seconds: msg = 'Timeout = {} Seconds'.format(seconds)
717
+ if instr: msg += ' With STDIN:\n' + instr
718
+ if msg: pglog(msg, cmdact)
719
+ stdlog = act if cmdopt&2 else 0
720
+ cmdflg = cmdact|stdlog
721
+ abort = -1 if cmdopt&64 else 0
722
+ loops = 2 if cmdopt&128 else 1
723
+ PGLOG['SYSERR'] = error = retbuf = outbuf = errbuf = ''
724
+ for loop in range(1, loops+1):
725
+ last = time.time()
726
+ try:
727
+ if instr:
728
+ FD = Popen(execmd, shell=doshell, stdout=PIPE, stderr=PIPE, stdin=PIPE)
729
+ if seconds:
730
+ outbuf, errbuf = FD.communicate(input=instr.encode(), timeout=seconds)
731
+ else:
732
+ outbuf, errbuf = FD.communicate(input=instr.encode())
733
+ else:
734
+ FD = Popen(execmd, shell=doshell, stdout=PIPE, stderr=PIPE)
735
+ if seconds:
736
+ outbuf, errbuf = FD.communicate(timeout=seconds)
737
+ else:
738
+ outbuf, errbuf = FD.communicate()
739
+ except TimeoutError as e:
740
+ errbuf = str(e)
741
+ FD.kill()
742
+ ret = FAILURE
743
+ except Exception as e:
744
+ errbuf = str(e)
745
+ ret = FAILURE
746
+ else:
747
+ ret = FAILURE if FD.returncode else SUCCESS
748
+ if isinstance(outbuf, bytes): outbuf = str(outbuf, errors='replace')
749
+ if isinstance(errbuf, bytes): errbuf = str(errbuf, errors='replace')
750
+
751
+ if errbuf and cmdopt&32:
752
+ outbuf += errbuf
753
+ if cmdopt&256: PGLOG['SYSERR'] = errbuf
754
+ errbuf = ''
755
+
756
+ if outbuf:
757
+ lines = outbuf.split('\n')
758
+ for line in lines:
759
+ line = strip_output_line(line.strip())
760
+ if not line: continue
761
+ if PGLOG['STD2ERR'] and std2err(line):
762
+ if cmdopt&260: error += line + "\n"
763
+ if abort == -1 and re.match('ABORTS ', line): abort = 1
764
+ else:
765
+ if re.match(r'^>+ ', line):
766
+ line = '>' + line
767
+ if cmdflg: pglog(line, cmdflg)
768
+ elif stdlog:
769
+ pglog(line, stdlog)
770
+ if cmdopt&16: retbuf += line + "\n"
771
+
772
+ if errbuf:
773
+ lines = errbuf.split('\n')
774
+ for line in lines:
775
+ line = strip_output_line(line.strip())
776
+ if not line: continue
777
+ if PGLOG['ERR2STD'] and err2std(line):
778
+ if stdlog: pglog(line, stdlog)
779
+ if cmdopt&16: retbuf += line + "\n"
780
+ else:
781
+ if cmdopt&260: error += line + "\n"
782
+ if abort == -1 and re.match('ABORTS ', line): abort = 1
783
+
784
+ if ret == SUCCESS and abort == 1: ret = FAILURE
785
+ end = time.time()
786
+ last = end - last
787
+
788
+ if error:
789
+ if ret == FAILURE:
790
+ error = "Error Execute: {}\n{}".format(cmdstr, error)
791
+ else:
792
+ error = "Error From: {}\n{}".format(cmdstr, error)
793
+
794
+ if loop > 1: error = "Retry "
795
+ if cmdopt&256: PGLOG['SYSERR'] += error
796
+ if cmdopt&4:
797
+ errlog = (act|ERRLOG)
798
+ if ret == FAILURE and loop >= loops: errlog |= logact
799
+ pglog(error, errlog)
800
+
801
+ if last > PGLOG['CMDTIME'] and not re.search(r'(^|/|\s)(dsarch|dsupdt|dsrqst)\s', cmdstr):
802
+ cmdstr = "> {} Ends By {}".format(break_long_string(cmdstr, 100, "...", 1), current_datetime())
803
+ cmd_execute_time(cmdstr, last, cmdact)
804
+
805
+ if ret == SUCCESS or loop >= loops: break
806
+ time.sleep(6)
807
+
808
+ if ret == FAILURE and retbuf and cmdopt&272 == 272:
809
+ if PGLOG['SYSERR']: PGLOG['SYSERR'] += '\n'
810
+ PGLOG['SYSERR'] += retbuf
811
+ retbuf = ''
812
+
813
+ return (retbuf if cmdopt&16 else ret)
814
+
815
+ #
816
+ # strip carrage return '\r', but keep ending newline '\n'
817
+ #
818
+ def strip_output_line(line):
819
+
820
+ ms = re.search(r'\r([^\r]+)\r*$', line)
821
+ if ms: return ms.group(1)
822
+
823
+ ms = re.search(r'\s\.+\s+(\d+)%\s+', line)
824
+ if ms and int(ms.group(1)) != 100: return None
825
+
826
+ return line
827
+
828
+ #
829
+ # show command running time string formated by seconds_to_string_time()
830
+ #
831
+ def cmd_execute_time(cmdstr, last, logact = None):
832
+
833
+ msg = cmdstr
834
+
835
+ if last >= PGLOG['CMDTIME']: # show running for at least one minute
836
+ msg += " ({})".format(seconds_to_string_time(last))
837
+
838
+ if logact:
839
+ return pglog(msg, logact)
840
+ else:
841
+ return msg
842
+
843
+ #
844
+ # convert given seconds to string time with units of S-Second,M-Minute,H-Hour,D-Day
845
+ #
846
+ def seconds_to_string_time(seconds, showzero = 0):
847
+
848
+ msg = ''
849
+ s = m = h = 0
850
+
851
+ if seconds > 0:
852
+ s = seconds%60 # seconds (0-59)
853
+ minutes = int(seconds/60) # total minutes
854
+ m = minutes%60 # minutes (0-59)
855
+ if minutes >= 60:
856
+ hours = int(minutes/60) # total hours
857
+ h = hours%24 # hours (0-23)
858
+ if hours >= 24:
859
+ msg += "{}D".format(int(hours/24)) # days
860
+ if h: msg += "{}H".format(h)
861
+ if m: msg += "{}M".format(m)
862
+ if s:
863
+ msg += "%dS"%(s) if isinstance(s, int) else "{:.3f}S".format(s)
864
+ elif showzero:
865
+ msg = "0S"
866
+
867
+ return msg
868
+
869
+ #
870
+ # wrap function to call pgsystem() with a timeout control
871
+ # return FAILURE if error eval or time out
872
+ #
873
+ def tosystem(cmd, timeout = 0, logact = LOGWRN, cmdopt = 5, instr = None):
874
+
875
+ if not timeout: timeout = PGLOG['TIMEOUT'] # set default timeout if missed
876
+ return pgsystem(cmd, logact, cmdopt, instr, timeout)
877
+
878
+ #
879
+ # insert breaks, default to '\n', for every length, default to 1024,
880
+ # for long string; return specified number lines if mline given
881
+ #
882
+ def break_long_string(lstr, limit = 1024, bsign = "\n", mline = 200, bchars = ' &;', minlmt = 20, eline = 0):
883
+
884
+ length = len(lstr) if lstr else 0
885
+ if length <= limit: return lstr
886
+
887
+ if bsign is None: bsign = "\n"
888
+ if bchars is None: bchars = ' &;'
889
+ addbreak = offset = 0
890
+ retstr = ""
891
+ elines = []
892
+ if eline > mline: eline = mline
893
+ mcnt = mline - eline
894
+ ecnt = 0
895
+
896
+ while offset < length:
897
+ bpos = lstr[offset:].find(bsign)
898
+ blen = bpos if bpos > -1 else (length - offset)
899
+ if blen == 0:
900
+ offset += 1
901
+ substr = "" if addbreak else bsign
902
+ addbreak = 0
903
+ elif blen <= limit:
904
+ blen += 1
905
+ substr = lstr[offset:(offset+blen)]
906
+ offset += blen
907
+ addbreak = 0
908
+ else:
909
+ substr = lstr[offset:(offset+limit)]
910
+ bpos = limit - 1
911
+ while bpos > minlmt:
912
+ char = substr[bpos]
913
+ if bchars.find(char) >= 0: break
914
+ bpos -= 1
915
+ if bpos > minlmt:
916
+ bpos += 1
917
+ substr = substr[:bpos]
918
+ offset += bpos
919
+ else:
920
+ offset += limit
921
+ addbreak = 1
922
+ substr += bsign
923
+
924
+ if mcnt:
925
+ retstr += substr
926
+ mcnt -= 1
927
+ if mcnt == 0 and eline == 0: break
928
+ elif eline > 0:
929
+ elines.append(substr)
930
+ ecnt += 1
931
+ else:
932
+ break
933
+
934
+ if ecnt > 0:
935
+ if ecnt > eline:
936
+ retstr += "..." + bsign
937
+ mcnt = ecnt - eline
938
+ else:
939
+ mcnt = 0
940
+
941
+ while mcnt < ecnt:
942
+ retstr += elines[mcnt]
943
+ mcnt += 1
944
+
945
+ return retstr
946
+
947
+ #
948
+ # join two paths by remove overlapping directories
949
+ # diff = 0: join given pathes
950
+ # 1: remove path1 from path2
951
+ #
952
+ def join_paths(path1, path2, diff = 0):
953
+
954
+ if not path2: return path1
955
+ if not path1 or not diff and re.match('/', path2): return path2
956
+
957
+ if diff:
958
+ ms = re.match(r'{}/(.*)'.format(path1), path2)
959
+ if ms: return ms.group(1)
960
+
961
+ adir1 = path1.split('/')
962
+ adir2 = path2.split('/')
963
+ while adir2 and not adir2[0]: adir2.pop(0)
964
+ while adir1 and adir2 and adir2[0] == "..":
965
+ adir2.pop(0)
966
+ adir1.pop()
967
+ while adir2 and adir2[0] == ".": adir2.pop(0)
968
+
969
+ if adir1 and adir2:
970
+ len1 = len(adir1)
971
+ len2 = len(adir2)
972
+ idx1 = len1-1
973
+ idx2 = mcnt = 0
974
+ while idx2 < len1 and idx2 < len2:
975
+ if adir1[idx1] == adir2[idx2]:
976
+ mcnt = 1
977
+ break
978
+ idx2 += 1
979
+
980
+ if mcnt > 0:
981
+ while mcnt <= idx2:
982
+ if adir1[idx1-mcnt] != adir2[idx2-mcnt]: break
983
+ mcnt += 1
984
+
985
+ if mcnt > idx2: # remove mcnt matching directories
986
+ while mcnt > 0:
987
+ adir2.pop(0)
988
+ mcnt -= 1
989
+
990
+ if diff:
991
+ return '/'.join(adir2)
992
+ else:
993
+ return '/'.join(adir1 + adir2)
994
+
995
+ #
996
+ # validate if a command for a given BATCH host is accessable and executable
997
+ #
998
+ # Return SUCCESS if valid; FAILURE if not
999
+ #
1000
+ def valid_batch_host(host, logact = 0):
1001
+
1002
+ HOST = host.upper()
1003
+ return SUCCESS if HOST in BCHCMDS and valid_command(BCHCMDS[HOST], logact) else FAILURE
1004
+
1005
+ #
1006
+ # validate if a given command is accessable and executable
1007
+ #
1008
+ # Return the full command path if valid; '' if not
1009
+ #
1010
+ def valid_command(cmd, logact = 0):
1011
+
1012
+ ms = re.match(r'^(\S+)( .*)$', cmd)
1013
+ if ms:
1014
+ option = ms.group(2)
1015
+ cmd = ms.group(1)
1016
+ else:
1017
+ option = ''
1018
+ if cmd not in COMMANDS:
1019
+ buf = shutil.which(cmd)
1020
+ if buf is None:
1021
+ if logact: pglog(cmd + ": executable command not found", logact)
1022
+ buf = ''
1023
+ elif option:
1024
+ buf += option
1025
+ COMMANDS[cmd] = buf
1026
+
1027
+ return COMMANDS[cmd]
1028
+
1029
+ #
1030
+ # add carbon copies to PGLOG['CCDADDR']
1031
+ #
1032
+ def add_carbon_copy(cc = None, isstr = None, exclude = 0, specialist = None):
1033
+
1034
+
1035
+ if not cc:
1036
+ if cc is None and isstr is None: PGLOG['CCDADDR'] = ''
1037
+ else:
1038
+ emails = re.split(r'[,\s]+', cc) if isstr else cc
1039
+ for email in emails:
1040
+ if not email or email.find('/') >= 0 or email == 'N': continue
1041
+ if email == "S":
1042
+ if not specialist: continue
1043
+ email = specialist
1044
+
1045
+ if email.find('@') == -1: email += "@ucar.edu"
1046
+ if exclude and exclude.find(email) > -1: continue
1047
+ if PGLOG['CCDADDR']:
1048
+ if PGLOG['CCDADDR'].find(email) > -1: continue # email Cc'd already
1049
+ PGLOG['CCDADDR'] += ", "
1050
+ PGLOG['CCDADDR'] += email
1051
+
1052
+ #
1053
+ # get the current host name; or batch sever name if getbatch is 1
1054
+ #
1055
+ def get_host(getbatch = 0):
1056
+
1057
+ if getbatch and PGLOG['CURBID'] != 0:
1058
+ host = PGLOG['PGBATCH']
1059
+ elif PGLOG['HOSTNAME']:
1060
+ return PGLOG['HOSTNAME']
1061
+ else:
1062
+ host = socket.gethostname()
1063
+
1064
+ return get_short_host(host)
1065
+
1066
+ #
1067
+ # strip domain names and retrun the server name itself
1068
+ #
1069
+ def get_short_host(host):
1070
+
1071
+ if not host: return ''
1072
+ ms = re.match(r'^([^\.]+)\.', host)
1073
+ if ms: host = ms.group(1)
1074
+ if PGLOG['HOSTNAME'] and (host == 'localhost' or host == PGLOG['HOSTNAME']): return PGLOG['HOSTNAME']
1075
+ HOST = host.upper()
1076
+ if HOST in BCHCMDS: return HOST
1077
+
1078
+ return host
1079
+
1080
+ #
1081
+ # get a live SLURM host name
1082
+ #
1083
+ def get_slurm_host():
1084
+
1085
+ global SLMHOSTS
1086
+
1087
+ if not SLMSTATS and PGLOG['SLMHOSTS']:
1088
+ SLMHOSTS = PGLOG['SLMHOSTS'].split(':')
1089
+ for host in SLMHOSTS:
1090
+ SLMSTATS[host] = 1
1091
+
1092
+ for host in SLMHOSTS:
1093
+ if host in SLMSTATS and SLMSTATS[host]: return host
1094
+
1095
+ return None
1096
+
1097
+ #
1098
+ # get a live PBS host name
1099
+ #
1100
+ def get_pbs_host():
1101
+
1102
+ global PBSHOSTS
1103
+
1104
+ if not PBSSTATS and PGLOG['PBSHOSTS']:
1105
+ PBSHOSTS = PGLOG['PBSHOSTS'].split(':')
1106
+ for host in PBSHOSTS:
1107
+ PBSSTATS[host] = 1
1108
+
1109
+ for host in PBSHOSTS:
1110
+ if host in PBSSTATS and PBSSTATS[host]: return host
1111
+
1112
+ return None
1113
+
1114
+ #
1115
+ # set host status, 0 dead & 1 live, for one or all avalaible slurm hosts
1116
+ #
1117
+ def set_slurm_host(host = None, stat = 0):
1118
+
1119
+ global SLMHOSTS
1120
+
1121
+ if host:
1122
+ SLMSTATS[host] = stat
1123
+ else:
1124
+ if not SLMHOSTS and PGLOG['SLMHOSTS']:
1125
+ SLMHOSTS = PGLOG['SLMHOSTS'].split(':')
1126
+ for host in SLMHOSTS:
1127
+ SLMSTATS[host] = stat
1128
+
1129
+ #
1130
+ # set host status, 0 dead & 1 live, for one or all avalaible pbs hosts
1131
+ #
1132
+ def set_pbs_host(host = None, stat = 0):
1133
+
1134
+ global PBSHOSTS
1135
+
1136
+ if host:
1137
+ PBSSTATS[host] = stat
1138
+ else:
1139
+ if not PBSHOSTS and PGLOG['PBSHOSTS']:
1140
+ PBSHOSTS = PGLOG['PBSHOSTS'].split(':')
1141
+ for host in PBSHOSTS:
1142
+ PBSSTATS[host] = stat
1143
+
1144
+ #
1145
+ # reset the batch host name in case was not set properly
1146
+ #
1147
+ def reset_batch_host(bhost, logact = LOGWRN):
1148
+
1149
+ BCHHOST = bhost.upper()
1150
+
1151
+ if BCHHOST != PGLOG['PGBATCH']:
1152
+ if PGLOG['CURBID'] > 0:
1153
+ pglog("{}-{}: Batch ID is set, cannot change Batch host to {}".format(PGLOG['PGBATCH'], PGLOG['CURBID'], BCHHOST) , logact)
1154
+ else:
1155
+ ms = re.search(r'(^|:){}(:|$)'.format(BCHHOST), PGLOG['BCHHOSTS'])
1156
+ if ms:
1157
+ PGLOG['PGBATCH'] = BCHHOST
1158
+ if PGLOG['CURBID'] == 0: PGLOG['CURBID'] = -1
1159
+ elif PGLOG['PGBATCH']:
1160
+ PGLOG['PGBATCH'] = ''
1161
+ PGLOG['CURBID'] = 0
1162
+
1163
+ #
1164
+ # return the base command name of the current process
1165
+ #
1166
+ def get_command(cmdstr = None):
1167
+
1168
+ if not cmdstr: cmdstr = sys.argv[0]
1169
+ cmdstr = op.basename(cmdstr)
1170
+ ms = re.match(r'^(.+)\.(py|pl)$', cmdstr)
1171
+ if ms:
1172
+ return ms.group(1)
1173
+ else:
1174
+ return cmdstr
1175
+
1176
+ #
1177
+ # wrap a given command cmd for either sudo or setuid wrapper pgstart_['username']
1178
+ # to run as user asuser
1179
+ #
1180
+ def get_local_command(cmd, asuser = None):
1181
+
1182
+ cuser = PGLOG['SETUID'] if PGLOG['SETUID'] else PGLOG['CURUID']
1183
+ if not asuser or cuser == asuser: return cmd
1184
+
1185
+ if cuser == PGLOG['GDEXUSER']:
1186
+ wrapper = "pgstart_" + asuser
1187
+ if valid_command(wrapper): return "{} {}".format(wrapper, cmd)
1188
+ elif PGLOG['SUDOGDEX'] and asuser == PGLOG['GDEXUSER']:
1189
+ return "sudo -u {} {}".format(PGLOG['GDEXUSER'], cmd) # sudo as user gdexdata
1190
+
1191
+ return cmd
1192
+
1193
+ #
1194
+ # wrap a given command cmd for either sudo or setuid wrapper pgstart_['username']
1195
+ # to run as user asuser on a given remote host
1196
+ #
1197
+ def get_remote_command(cmd, host, asuser = None):
1198
+
1199
+ # if host and not re.match(PGLOG['HOSTNAME'], host): cmd = "ssh {} {}".format(host, cmd)
1200
+ return get_local_command(cmd, asuser)
1201
+
1202
+ #
1203
+ # wrap a given hpss command cmd with sudo either before his of after hsi
1204
+ # to run as user asuser
1205
+ #
1206
+ def get_hpss_command(cmd, asuser = None, hcmd = None):
1207
+
1208
+ cuser = PGLOG['SETUID'] if PGLOG['SETUID'] else PGLOG['CURUID']
1209
+ if not hcmd: hcmd = 'hsi'
1210
+
1211
+ if asuser and cuser != asuser:
1212
+ if cuser == PGLOG['GDEXUSER']:
1213
+ return "{} sudo -u {} {}".format(hcmd, asuser, cmd) # setuid wrapper as user asuser
1214
+ elif PGLOG['SUDOGDEX'] and asuser == PGLOG['GDEXUSER']:
1215
+ return "sudo -u {} {} {}".format(PGLOG['GDEXUSER'], hcmd, cmd) # sudo as user gdexdata
1216
+
1217
+ if cuser != PGLOG['GDEXUSER']:
1218
+ if re.match(r'^ls ', cmd) and hcmd == 'hsi':
1219
+ return "hpss" + cmd # use 'hpssls' instead of 'hsi ls'
1220
+ elif re.match(r'^htar -tvf', hcmd):
1221
+ hcmd.replace('htar -tvf', 'htarmember', 1) # use 'htarmember' instead of 'htar -tvf'
1222
+ elif re.match(r'^hsi ls', hcmd):
1223
+ hcmd.replce('hsi ls', 'hpssls', 1) # use 'hpssls' instead of 'hsi ls'
1224
+
1225
+ return "{} {}".format(hcmd, cmd)
1226
+
1227
+ #
1228
+ # wrap a given sync command for given host name with/without sudo
1229
+ #
1230
+ def get_sync_command(host, asuser = None):
1231
+
1232
+ host = get_short_host(host)
1233
+
1234
+ if (not (PGLOG['SETUID'] and PGLOG['SETUID'] == PGLOG['GDEXUSER']) and
1235
+ (not asuser or asuser == PGLOG['GDEXUSER'])):
1236
+ return "sync" + host
1237
+
1238
+ return host + "-sync"
1239
+
1240
+ #
1241
+ # set PGLOG['SETUID'] as needed
1242
+ #
1243
+ def set_suid(cuid = 0):
1244
+
1245
+ if not cuid: cuid = PGLOG['EUID']
1246
+ if cuid != PGLOG['EUID'] or cuid != PGLOG['RUID']:
1247
+ os.setreuid(cuid, cuid)
1248
+ PGLOG['SETUID'] = pwd.getpwuid(cuid).pw_name
1249
+ if not (PGLOG['SETUID'] == PGLOG['GDEXUSER'] or cuid == PGLOG['RUID']):
1250
+ set_specialist_environments(PGLOG['SETUID'])
1251
+ PGLOG['CURUID'] == PGLOG['SETUID'] # set CURUID to a specific specialist
1252
+
1253
+ #
1254
+ # set comman pglog
1255
+ #
1256
+ def set_common_pglog():
1257
+
1258
+ PGLOG['CURDIR'] = os.getcwd()
1259
+
1260
+ # set current user id
1261
+ PGLOG['RUID'] = os.getuid()
1262
+ PGLOG['EUID'] = os.geteuid()
1263
+ PGLOG['CURUID'] = pwd.getpwuid(PGLOG['RUID']).pw_name
1264
+ try:
1265
+ PGLOG['RDAUID'] = PGLOG['GDEXUID'] = pwd.getpwnam(PGLOG['GDEXUSER']).pw_uid
1266
+ PGLOG['RDAGID'] = PGLOG['GDEXGID'] = grp.getgrnam(PGLOG['GDEXGRP']).gr_gid
1267
+ except:
1268
+ PGLOG['RDAUID'] = PGLOG['GDEXUID'] = 0
1269
+ PGLOG['RDAGID'] = PGLOG['GDEXGID'] = 0
1270
+ if PGLOG['CURUID'] == PGLOG['GDEXUSER']: PGLOG['SETUID'] = PGLOG['GDEXUSER']
1271
+
1272
+ PGLOG['HOSTNAME'] = get_host()
1273
+ for htype in HOSTTYPES:
1274
+ ms = re.match(r'^{}(-|\d|$)'.format(htype), PGLOG['HOSTNAME'])
1275
+ if ms:
1276
+ PGLOG['HOSTTYPE'] = HOSTTYPES[htype]
1277
+ break
1278
+ PGLOG['DEFDSID'] = 'd000000' if PGLOG['NEWDSID'] else 'ds000.0'
1279
+ PGLOG['NOTAROOT'] = '|'.join([PGLOG['OLDAROOT'], PGLOG['OLDBROOT'], PGLOG['BACKROOT']])
1280
+ PGLOG['NOTBROOT'] = '|'.join([PGLOG['OLDAROOT'], PGLOG['OLDBROOT'], PGLOG['ARCHROOT']])
1281
+ PGLOG['ALLROOTS'] = '|'.join([PGLOG['OLDAROOT'], PGLOG['OLDBROOT'], PGLOG['ARCHROOT'], PGLOG['BACKROOT']])
1282
+ SETPGLOG("USRHOME", "/glade/u/home")
1283
+ SETPGLOG("DSSHOME", "/glade/u/home/gdexdata")
1284
+ SETPGLOG("GDEXHOME", "/data/local")
1285
+ SETPGLOG("ADDPATH", "")
1286
+ SETPGLOG("ADDLIB", "")
1287
+ SETPGLOG("OTHPATH", "")
1288
+ SETPGLOG("PSQLHOME", "/usr/pgsql-15")
1289
+ SETPGLOG("DSGHOSTS", "")
1290
+ SETPGLOG("DSIDCHRS", "d")
1291
+
1292
+ if not os.getenv('HOME'): os.environ['HOME'] = "{}/{}".format(PGLOG['USRHOME'], PGLOG['CURUID'])
1293
+ SETPGLOG("HOMEBIN", os.environ.get('HOME') + "/bin")
1294
+
1295
+ if 'SLURM_JOBID' in os.environ:
1296
+ PGLOG['CURBID'] = int(os.getenv('SLURM_JOBID'))
1297
+ PGLOG['PGBATCH'] = PGLOG['SLMNAME']
1298
+ elif 'PBS_JOBID' in os.environ:
1299
+ sbid = os.getenv('PBS_JOBID')
1300
+ ms = re.match(r'^(\d+)', sbid)
1301
+ PGLOG['CURBID'] = int(ms.group(1)) if ms else -1
1302
+ PGLOG['PGBATCH'] = PGLOG['PBSNAME']
1303
+ else:
1304
+ PGLOG['CURBID'] = 0
1305
+ PGLOG['PGBATCH'] = ''
1306
+
1307
+ pgpath = PGLOG['HOMEBIN']
1308
+ PGLOG['LOCHOME'] = "/ncar/rda/setuid"
1309
+ if not op.isdir(PGLOG['LOCHOME']): PGLOG['LOCHOME'] = "/usr/local/decs"
1310
+ pgpath += ":{}/bin".format(PGLOG['LOCHOME'])
1311
+ locpath = "{}/bin/{}".format(PGLOG['DSSHOME'], PGLOG['HOSTTYPE'])
1312
+ if op.isdir(locpath): pgpath += ":" + locpath
1313
+ pgpath = add_local_path("{}/bin".format(PGLOG['DSSHOME']), pgpath, 1)
1314
+ if PGLOG['PSQLHOME']:
1315
+ locpath = PGLOG['PSQLHOME'] + "/bin"
1316
+ if op.isdir(locpath): pgpath += ":" + locpath
1317
+ pgpath = add_local_path(os.getenv('PATH'), pgpath, 1)
1318
+ if PGLOG['HOSTTYPE'] == 'dav': pgpath = add_local_path('/glade/u/apps/opt/qstat-cache/bin:/opt/pbs/bin', pgpath, 1)
1319
+ if 'OTHPATH' in PGLOG and PGLOG['OTHPATH']:
1320
+ pgpath = add_local_path(PGLOG['OTHPATH'], pgpath, 1)
1321
+ if PGLOG['ADDPATH']: pgpath = add_local_path(PGLOG['ADDPATH'], pgpath, 1)
1322
+ pgpath = add_local_path("/bin:/usr/bin:/usr/local/bin:/usr/sbin", pgpath, 1)
1323
+
1324
+ os.environ['PATH'] = pgpath
1325
+ os.environ['SHELL'] = '/bin/sh'
1326
+ # set PGLOG values with environments and defaults
1327
+ SETPGLOG("DSSDBHM", PGLOG['DSSHOME']+"/dssdb") # dssdb home dir
1328
+ SETPGLOG("LOGPATH", PGLOG['DSSDBHM']+"/log") # path to log file
1329
+ SETPGLOG("LOGFILE", "pgdss.log") # log file name
1330
+ SETPGLOG("EMLFILE", "pgemail.log") # email log file name
1331
+ SETPGLOG("ERRFILE", '') # error file name
1332
+ sm = "/usr/sbin/sendmail"
1333
+ if valid_command(sm): SETPGLOG("EMLSEND", f"{sm} -t") # send email command
1334
+ SETPGLOG("DBGLEVEL", '') # debug level
1335
+ SETPGLOG("BAOTOKEN", 's.lh2t2kDjrqs3V8y2BU2zOocT') # OpenBao token
1336
+ SETPGLOG("DBGPATH", PGLOG['DSSDBHM']+"/log") # path to debug log file
1337
+ SETPGLOG("OBJCTBKT", "gdex-data") # default Bucket on Object Store
1338
+ SETPGLOG("BACKUPEP", "gdex-quasar") # default Globus Endpoint on Quasar
1339
+ SETPGLOG("DRDATAEP", "gdex-quasar-drdata") # DRDATA Globus Endpoint on Quasar
1340
+ SETPGLOG("DBGFILE", "pgdss.dbg") # debug file name
1341
+ SETPGLOG("CNFPATH", PGLOG['DSSHOME']+"/config") # path to configuration files
1342
+ SETPGLOG("DSSURL", "https://gdex.ucar.edu") # current dss web URL
1343
+ SETPGLOG("RQSTURL", "/datasets/request") # request URL path
1344
+ SETPGLOG("WEBSERVERS", "") # webserver names for Web server
1345
+ PGLOG['WEBHOSTS'] = PGLOG['WEBSERVERS'].split(':') if PGLOG['WEBSERVERS'] else []
1346
+ SETPGLOG("DBMODULE", '')
1347
+ SETPGLOG("LOCDATA", "/data")
1348
+
1349
+ # set dss web homedir
1350
+ SETPGLOG("DSSWEB", PGLOG['LOCDATA']+"/web")
1351
+ SETPGLOG("DSWHOME", PGLOG['DSSWEB']+"/datasets") # datast web root path
1352
+ PGLOG['HOMEROOTS'] = "{}|{}".format(PGLOG['DSSHOME'], PGLOG['DSWHOME'])
1353
+ SETPGLOG("DSSDATA", "/glade/campaign/collections/gdex") # dss data root path
1354
+ SETPGLOG("DSDHOME", PGLOG['DSSDATA']+"/data") # dataset data root path
1355
+ SETPGLOG("DECSHOME", PGLOG['DSSDATA']+"/decsdata") # dataset decsdata root path
1356
+ SETPGLOG("DSHHOME", PGLOG['DECSHOME']+"/helpfiles") # dataset help root path
1357
+ SETPGLOG("GDEXWORK", "/lustre/desc1/gdex/work") # gdex work path
1358
+ SETPGLOG("UPDTWKP", PGLOG['GDEXWORK']) # dsupdt work root path
1359
+ SETPGLOG("TRANSFER", "/lustre/desc1/gdex/transfer") # gdex transfer path
1360
+ SETPGLOG("RQSTHOME", PGLOG['TRANSFER']+"/dsrqst") # dsrqst home
1361
+ SETPGLOG("DSAHOME", "") # dataset data alternate root path
1362
+ SETPGLOG("RQSTALTH", "") # alternate dsrqst path
1363
+ SETPGLOG("GPFSHOST", "") # empty if writable to glade
1364
+ SETPGLOG("PSQLHOST", "rda-db.ucar.edu") # host name for postgresql server
1365
+ SETPGLOG("SLMHOSTS", "cheyenne:casper") # host names for SLURM server
1366
+ SETPGLOG("PBSHOSTS", "cron:casper") # host names for PBS server
1367
+ SETPGLOG("CHKHOSTS", "") # host names for dscheck daemon
1368
+ SETPGLOG("PVIEWHOST", "pgdb02.k8s.ucar.edu") # host name for view only postgresql server
1369
+ SETPGLOG("PMISCHOST", "pgdb03.k8s.ucar.edu") # host name for misc postgresql server
1370
+ SETPGLOG("FTPUPLD", PGLOG['TRANSFER']+"/rossby") # ftp upload path
1371
+ PGLOG['GPFSROOTS'] = "{}|{}|{}".format(PGLOG['DSDHOME'], PGLOG['UPDTWKP'], PGLOG['RQSTHOME'])
1372
+
1373
+ if 'ECCODES_DEFINITION_PATH' not in os.environ:
1374
+ os.environ['ECCODES_DEFINITION_PATH'] = "/usr/local/share/eccodes/definitions"
1375
+ os.environ['history'] = '0'
1376
+
1377
+ # set tmp dir
1378
+ SETPGLOG("TMPPATH", PGLOG['GDEXWORK'] + "/ptmp")
1379
+ if not PGLOG['TMPPATH']: PGLOG['TMPPATH'] = "/data/ptmp"
1380
+
1381
+ SETPGLOG("TMPDIR", '')
1382
+ if not PGLOG['TMPDIR']:
1383
+ PGLOG['TMPDIR'] = "/lustre/desc1/scratch/" + PGLOG['CURUID']
1384
+ os.environ['TMPDIR'] = PGLOG['TMPDIR']
1385
+
1386
+ # empty diretory for HOST-sync
1387
+
1388
+ PGLOG['TMPSYNC'] = PGLOG['DSSDBHM'] + "/tmp/.syncdir"
1389
+
1390
+ os.umask(2)
1391
+
1392
+ #
1393
+ # check and return TMPSYNC path, and add it if not exists
1394
+ #
1395
+ def get_tmpsync_path():
1396
+
1397
+ if 'DSSHOME' in PGLOG and PGLOG['DSSHOME'] and not op.exists(PGLOG['TMPSYNC']):
1398
+ pgsystem("mkdir " + PGLOG['TMPSYNC'], 0, LGWNEX, 4)
1399
+ pgsystem("chmod 775 " + PGLOG['TMPSYNC'], LOGWRN, 4)
1400
+ return PGLOG['TMPSYNC']
1401
+
1402
+ #
1403
+ # append or prepend locpath to pgpath
1404
+ #
1405
+ def add_local_path(locpath, pgpath, append = 0):
1406
+
1407
+ if not locpath:
1408
+ return pgpath
1409
+ elif not pgpath:
1410
+ return locpath
1411
+
1412
+ paths = locpath.split(':')
1413
+
1414
+ for path in paths:
1415
+ if re.match(r'^\./*$', path): continue
1416
+ path = path.rstrip('\\')
1417
+ ms = re.search(r'(^|:){}(:|$)'.format(path), pgpath)
1418
+ if ms: continue
1419
+ if append:
1420
+ pgpath += ":" + path
1421
+ else:
1422
+ pgpath = path + ":" + pgpath
1423
+
1424
+ return pgpath
1425
+
1426
+ #
1427
+ # set PGLOG value; return a string or an array reference if sep is not emty
1428
+ #
1429
+ def SETPGLOG(name, value = ''):
1430
+
1431
+ oval = PGLOG[name] if name in PGLOG else ''
1432
+ nval = get_environment(name, ('' if re.match('PG', value) else value))
1433
+ PGLOG[name] = nval if nval else oval
1434
+
1435
+ #
1436
+ # set specialist home and return the default shell
1437
+ #
1438
+ def set_specialist_home(specialist):
1439
+
1440
+ if specialist == PGLOG['CURUID']: return # no need reset
1441
+ if 'MAIL' in os.environ and re.search(PGLOG['CURUID'], os.environ['MAIL']):
1442
+ os.environ['MAIL'] = re.sub(PGLOG['CURUID'], specialist, os.environ['MAIL'])
1443
+
1444
+ home = "{}/{}".format(PGLOG['USRHOME'], specialist)
1445
+ shell = "tcsh"
1446
+ buf = pgsystem("grep ^{}: /etc/passwd".format(specialist), LOGWRN, 20)
1447
+ if buf:
1448
+ lines = buf.split('\n')
1449
+ for line in lines:
1450
+ ms = re.search(r':(/.+):(/.+)', line)
1451
+ if ms:
1452
+ home = ms.group(1)
1453
+ shell = op.basename(ms.group(2))
1454
+ break
1455
+
1456
+ if home != os.environ['HOME'] and op.exists(home):
1457
+ os.environ['HOME'] = home
1458
+
1459
+ return shell
1460
+
1461
+ #
1462
+ # set environments for a specified specialist
1463
+ #
1464
+ def set_specialist_environments(specialist):
1465
+
1466
+ shell = set_specialist_home(specialist)
1467
+ resource = os.environ['HOME'] + "/.tcshrc"
1468
+ checkif = 0 # 0 outside of if; 1 start if, 2 check envs, -1 checked already
1469
+ missthen = 0
1470
+ try:
1471
+ rf = open(resource, 'r')
1472
+ except:
1473
+ return # skip if cannot open
1474
+
1475
+ nline = rf.readline()
1476
+ while nline:
1477
+ line = pgtrim(nline)
1478
+ nline = rf.readline()
1479
+ if not line: continue
1480
+ if checkif == 0:
1481
+ ms = re.match(r'^if(\s|\()', line)
1482
+ if ms: checkif = 1 # start if
1483
+ elif missthen:
1484
+ missthen = 0
1485
+ if re.match(r'^then$', line): continue # then on next line
1486
+ checkif = 0 # end of inline if
1487
+ elif re.match(r'^endif', line):
1488
+ checkif = 0 # end of if
1489
+ continue
1490
+ elif checkif == -1: # skip the line
1491
+ continue
1492
+ elif checkif == 2 and re.match(r'^else', line):
1493
+ checkif = -1 # done check envs in if
1494
+ continue
1495
+
1496
+ if checkif == 1:
1497
+ if line == 'else':
1498
+ checkif = 2
1499
+ continue
1500
+ elif re.search(r'if\W', line):
1501
+ if(re.search(r'host.*!', line, re.I) and not re.search(PGLOG['HOSTNAME'], line) or
1502
+ re.search(r'host.*=', line, re.I) and re.search(PGLOG['HOSTNAME'], line)):
1503
+ checkif = 2
1504
+ if re.search(r'\sthen$', line):
1505
+ continue
1506
+ else:
1507
+ missthen = 1
1508
+ if checkif == 1: continue
1509
+ else:
1510
+ continue
1511
+
1512
+ ms = re.match(r'^setenv\s+(.*)', line)
1513
+ if ms: one_specialist_environment(ms.group(1))
1514
+
1515
+ rf.close()
1516
+
1517
+ SETPGLOG("HOMEBIN", PGLOG['PGBINDIR'])
1518
+ os.environ['PATH'] = add_local_path(PGLOG['HOMEBIN'], os.environ['PATH'], 0)
1519
+
1520
+ #
1521
+ # set one environment for specialist
1522
+ #
1523
+ def one_specialist_environment(line):
1524
+
1525
+ ms = re.match(r'^(\w+)[=\s]+(.+)$', line)
1526
+ if not ms: return
1527
+ (var, val) = ms.groups()
1528
+ if re.match(r'^(PATH|SHELL|IFS|CDPATH|)$', var): return
1529
+ if val.find('$') > -1: val = replace_environments(val)
1530
+ ms = re.match(r'^(\"|\')(.*)(\"|\')$', val)
1531
+ if ms: val = ms.group(2) # remove quotes
1532
+ os.environ[var] = val
1533
+
1534
+ #
1535
+ # get and repalce environment variables in ginve string; defaults to the values in PGLOG
1536
+ #
1537
+ def replace_environments(envstr, default = '', logact = 0):
1538
+
1539
+ ishash = isinstance(default, dict)
1540
+ ms = re.search(r'(^|.)\$({*)(\w+)(}*)', envstr)
1541
+ if ms:
1542
+ lead = ms.group(1)
1543
+ name = ms.group(3)
1544
+ rep = ms.group(2) + name + ms.group(4)
1545
+ env = get_environment(name, (PGLOG[name] if name in PGLOG else (default[name] if ishash else default)), logact)
1546
+ pre = (lead if (env or lead != ":") else '')
1547
+ envstr = re.sub(r'{}\${}'.format(lead, rep), (pre+env), envstr)
1548
+
1549
+ return envstr
1550
+
1551
+ #
1552
+ # validate if the current host is a valid host to process
1553
+ #
1554
+ def check_process_host(hosts, chost = None, mflag = None, pinfo = None, logact = None):
1555
+
1556
+ ret = 1
1557
+ error = ''
1558
+ if not mflag: mflag = 'G'
1559
+ if not chost: chost = get_host(1)
1560
+
1561
+ if mflag == 'M': # exact match
1562
+ if not hosts or hosts != chost:
1563
+ ret = 0
1564
+ if pinfo: error = "not matched exactly"
1565
+ elif mflag == 'I': # inclusive match
1566
+ if not hosts or hosts.find('!') == 0 or hosts.find(chost) < 0:
1567
+ ret = 0
1568
+ if pinfo: error = "not matched inclusively"
1569
+ elif hosts:
1570
+ if hosts.find(chost) >= 0:
1571
+ if hosts.find('!') == 0:
1572
+ ret = 0
1573
+ if pinfo: error = "matched exclusively"
1574
+ elif hosts.find('!') != 0:
1575
+ ret = 0
1576
+ if pinfo: error = "not matched"
1577
+
1578
+ if error:
1579
+ if logact is None: logact = LOGERR
1580
+ pglog("{}: CANNOT be processed on {} for hosthame {}".format(pinfo, chost, error), logact)
1581
+
1582
+ return ret
1583
+
1584
+ #
1585
+ # convert special foreign characters into ascii characters
1586
+ #
1587
+ def convert_chars(name, default = 'X'):
1588
+ if not name: return default
1589
+ if re.match(r'^[a-zA-Z0-9]+$', name): return name # conversion not needed
1590
+ decoded_name = unidecode(name).strip()
1591
+ # remove any non-alphanumeric and non-underscore characters
1592
+ cleaned_name = re.sub(r'[^a-zA-Z0-9_]', '', decoded_name)
1593
+ if cleaned_name:
1594
+ return cleaned_name
1595
+ else:
1596
+ return default
1597
+
1598
+ #
1599
+ # Retrieve host and process id
1600
+ #
1601
+ def current_process_info(realpid = 0):
1602
+
1603
+ if realpid or PGLOG['CURBID'] < 1:
1604
+ return [PGLOG['HOSTNAME'], os.getpid()]
1605
+ else:
1606
+ return [PGLOG['PGBATCH'], PGLOG['CURBID']]
1607
+
1608
+ #
1609
+ # convert given @ARGV to string. quote the entries with spaces
1610
+ #
1611
+ def argv_to_string(argv = None, quote = 1, action = None):
1612
+
1613
+ argstr = ''
1614
+ if argv is None: argv = sys.argv[1:]
1615
+ for arg in argv:
1616
+ if argstr: argstr += ' '
1617
+ ms = re.search(r'([<>\|\s])', arg)
1618
+ if ms:
1619
+ if action:
1620
+ pglog("{}: Cannot {} for special character '{}' in argument value".format(arg, action, ms.group(1)), LGEREX)
1621
+ if quote:
1622
+ if re.search(r"\'", arg):
1623
+ arg = "\"{}\"".format(arg)
1624
+ else:
1625
+ arg = "'{}'".format(arg)
1626
+ argstr += arg
1627
+
1628
+ return argstr
1629
+
1630
+ #
1631
+ # convert an integer to non-10 based string
1632
+ #
1633
+ def int2base(x, base):
1634
+
1635
+ if x == 0: return '0'
1636
+ negative = 0
1637
+ if x < 0:
1638
+ negative = 1
1639
+ x = -x
1640
+
1641
+ dgts = []
1642
+ while x:
1643
+ dgts.append(str(int(x%base)))
1644
+ x = int(x/base)
1645
+ if negative: dgts.append('-')
1646
+ dgts.reverse()
1647
+
1648
+ return ''.join(dgts)
1649
+
1650
+ #
1651
+ # convert a non-10 based string to an integer
1652
+ #
1653
+ def base2int(x, base):
1654
+
1655
+ if not isinstance(x, int): x = int(x)
1656
+ if x == 0: return 0
1657
+
1658
+ negative = 0
1659
+ if x < 0:
1660
+ negative = 1
1661
+ x = -x
1662
+
1663
+ num = 0
1664
+ fact = 1
1665
+ while x:
1666
+ num += (x%10)*fact
1667
+ fact *= base
1668
+ x = int(x/10)
1669
+ if negative: num = -num
1670
+
1671
+ return num
1672
+
1673
+ #
1674
+ # convert integer to ordinal string
1675
+ #
1676
+ def int2order(num):
1677
+
1678
+ ordstr = ['th', 'st', 'nd', 'rd']
1679
+ snum = str(num)
1680
+ num %= 100
1681
+ if num > 19: num %= 10
1682
+ if num > 3: num = 0
1683
+
1684
+ return snum + ordstr[num]
1685
+
1686
+ #
1687
+ # Always call this function to initialize global variables for all applications
1688
+ #
1689
+ set_common_pglog()