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