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