rda-python-common 2.1.9__tar.gz → 2.1.11__tar.gz
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-2.1.9/src/rda_python_common.egg-info → rda_python_common-2.1.11}/PKG-INFO +4 -4
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/README.md +1 -1
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/pyproject.toml +3 -3
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/__init__.py +1 -1
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_cmd.py +7 -1
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_file.py +18 -9
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_log.py +20 -17
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_sig.py +96 -81
- {rda_python_common-2.1.9 → rda_python_common-2.1.11/src/rda_python_common.egg-info}/PKG-INFO +4 -4
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/requires.txt +2 -2
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/LICENSE +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/setup.cfg +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgCMD.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgDBI.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgFile.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgLOG.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgLock.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgOPT.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgSIG.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgSplit.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/PgUtil.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_dbi.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_lock.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_opt.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_password.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_split.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pg_util.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pgpassword.py +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common/pgpassword.usg +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/entry_points.txt +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/top_level.txt +0 -0
- {rda_python_common-2.1.9 → rda_python_common-2.1.11}/test/test_common.py +0 -0
{rda_python_common-2.1.9/src/rda_python_common.egg-info → rda_python_common-2.1.11}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_common
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.11
|
|
4
4
|
Summary: RDA Python common library codes shared by other RDA python packages
|
|
5
5
|
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/NCAR/rda-python-common
|
|
@@ -11,8 +11,8 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
11
11
|
Requires-Python: >=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: psycopg2-binary
|
|
15
|
+
Requires-Dist: psutil
|
|
16
16
|
Requires-Dist: rda-python-globus
|
|
17
17
|
Requires-Dist: unidecode
|
|
18
18
|
Requires-Dist: hvac
|
|
@@ -92,7 +92,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
|
|
|
92
92
|
python -c "import rda_python_common; print(rda_python_common.__version__)"
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
You should see the installed version (currently `2.1.
|
|
95
|
+
You should see the installed version (currently `2.1.11`). If the import
|
|
96
96
|
fails, double-check that the active Python environment is the one where you
|
|
97
97
|
ran `pip install`.
|
|
98
98
|
|
|
@@ -72,7 +72,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
|
|
|
72
72
|
python -c "import rda_python_common; print(rda_python_common.__version__)"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
You should see the installed version (currently `2.1.
|
|
75
|
+
You should see the installed version (currently `2.1.11`). If the import
|
|
76
76
|
fails, double-check that the active Python environment is the one where you
|
|
77
77
|
ran `pip install`.
|
|
78
78
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rda_python_common"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.11"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Zaihua Ji", email="zji@ucar.edu" },
|
|
10
10
|
]
|
|
@@ -18,8 +18,8 @@ classifiers = [
|
|
|
18
18
|
"Development Status :: 5 - Production/Stable",
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"psycopg2-binary",
|
|
22
|
+
"psutil",
|
|
23
23
|
"rda-python-globus",
|
|
24
24
|
"unidecode",
|
|
25
25
|
"hvac"
|
|
@@ -389,7 +389,7 @@ class PgCMD(PgLock):
|
|
|
389
389
|
if pgrqst: pgctl = self.get_dsrqst_control(pgrqst, logact)
|
|
390
390
|
return pgctl
|
|
391
391
|
|
|
392
|
-
def get_dynamic_options(self, cmd, oindex, otype):
|
|
392
|
+
def get_dynamic_options(self, cmd, oindex, otype, hostname=None):
|
|
393
393
|
"""Runs cmd to retrieve dynamic option strings, retrying on timeout.
|
|
394
394
|
|
|
395
395
|
Executes the command up to three times, retrying when a connection
|
|
@@ -401,6 +401,11 @@ class PgCMD(PgLock):
|
|
|
401
401
|
oindex (int or None): Object index appended to cmd when truthy.
|
|
402
402
|
otype (str or None): Object type appended to cmd when truthy;
|
|
403
403
|
'R' selects the first option from a slash-separated pair.
|
|
404
|
+
hostname (str or None): If provided, the command is executed
|
|
405
|
+
remotely via ``ssh <hostname> ...`` instead of locally; the
|
|
406
|
+
leading command name is resolved to its absolute path via
|
|
407
|
+
``command_path`` so the remote shell does not depend on its
|
|
408
|
+
own PATH.
|
|
404
409
|
|
|
405
410
|
Returns:
|
|
406
411
|
str: The parsed option string, or '' if the command produced no
|
|
@@ -408,6 +413,7 @@ class PgCMD(PgLock):
|
|
|
408
413
|
"""
|
|
409
414
|
if oindex: cmd += " {}".format(oindex)
|
|
410
415
|
if otype: cmd += ' ' + otype
|
|
416
|
+
if hostname: cmd = "ssh {} {}".format(hostname, self.command_path(cmd))
|
|
411
417
|
ret = options = ''
|
|
412
418
|
for loop in range(3):
|
|
413
419
|
ret = self.pgsystem(cmd, self.LOGWRN, 1299) # 1+2+16+256+1024
|
|
@@ -19,6 +19,7 @@ import re
|
|
|
19
19
|
import time
|
|
20
20
|
import glob
|
|
21
21
|
import json
|
|
22
|
+
import hashlib
|
|
22
23
|
from .pg_util import PgUtil
|
|
23
24
|
from .pg_sig import PgSIG
|
|
24
25
|
|
|
@@ -2874,24 +2875,32 @@ class PgFile(PgUtil, PgSIG):
|
|
|
2874
2875
|
(with None for missing files) for multiple files, or None
|
|
2875
2876
|
on failure.
|
|
2876
2877
|
"""
|
|
2877
|
-
cmd = 'md5sum '
|
|
2878
2878
|
if count > 0:
|
|
2879
2879
|
checksum = [None]*count
|
|
2880
2880
|
for i in range(count):
|
|
2881
2881
|
if op.isfile(file[i]):
|
|
2882
|
-
|
|
2883
|
-
if chksm:
|
|
2884
|
-
ms = re.search(r'(\w{32})', chksm)
|
|
2885
|
-
if ms: checksum[i] = ms.group(1)
|
|
2882
|
+
checksum[i] = self._file_md5(file[i], logact)
|
|
2886
2883
|
else:
|
|
2887
2884
|
checksum = None
|
|
2888
2885
|
if op.isfile(file):
|
|
2889
|
-
|
|
2890
|
-
if chksm:
|
|
2891
|
-
ms = re.search(r'(\w{32})', chksm)
|
|
2892
|
-
if ms: checksum = ms.group(1)
|
|
2886
|
+
checksum = self._file_md5(file, logact)
|
|
2893
2887
|
return checksum
|
|
2894
2888
|
|
|
2889
|
+
def _file_md5(self, path, logact=0):
|
|
2890
|
+
"""Compute MD5 hex digest of *path*, reading in 1 MiB chunks.
|
|
2891
|
+
|
|
2892
|
+
Returns the hex digest string, or None on read error.
|
|
2893
|
+
"""
|
|
2894
|
+
try:
|
|
2895
|
+
h = hashlib.md5()
|
|
2896
|
+
with open(path, 'rb') as fh:
|
|
2897
|
+
for chunk in iter(lambda: fh.read(1048576), b''):
|
|
2898
|
+
h.update(chunk)
|
|
2899
|
+
return h.hexdigest()
|
|
2900
|
+
except OSError as e:
|
|
2901
|
+
self.pglog("Error md5sum {}: {}".format(path, str(e)), logact)
|
|
2902
|
+
return None
|
|
2903
|
+
|
|
2895
2904
|
# Evaluate md5 checksums and compare them for two given files
|
|
2896
2905
|
# file1, file2: file names
|
|
2897
2906
|
# Return: 0 if same and 1 if not
|
|
@@ -18,6 +18,7 @@ import grp
|
|
|
18
18
|
import shlex
|
|
19
19
|
import smtplib
|
|
20
20
|
from email.message import EmailMessage
|
|
21
|
+
import subprocess
|
|
21
22
|
from subprocess import Popen, PIPE
|
|
22
23
|
from os import path as op
|
|
23
24
|
import time
|
|
@@ -180,6 +181,7 @@ class PgLOG:
|
|
|
180
181
|
self.BCHCMDS = {'PBS': 'qsub'}
|
|
181
182
|
# global dists to cashe information
|
|
182
183
|
self.COMMANDS = {}
|
|
184
|
+
self.CMDPATHS = {} # cache of bare command name -> full path (or '' if not found)
|
|
183
185
|
self.PBSHOSTS = []
|
|
184
186
|
self.PBSSTATS = {}
|
|
185
187
|
# set additional common PGLOG values
|
|
@@ -768,7 +770,7 @@ class PgLOG:
|
|
|
768
770
|
nilcnt = 0
|
|
769
771
|
if begin: sys.stdout.write(line)
|
|
770
772
|
else:
|
|
771
|
-
|
|
773
|
+
subprocess.run(['more', usgname])
|
|
772
774
|
self.pgexit(0)
|
|
773
775
|
|
|
774
776
|
def err2std(self, line):
|
|
@@ -1197,15 +1199,19 @@ class PgLOG:
|
|
|
1197
1199
|
Returns:
|
|
1198
1200
|
String with the leading command resolved to its full path, or the
|
|
1199
1201
|
original *cmdstr* if the command already contains a path separator
|
|
1200
|
-
or cannot be found via ``shutil.which``.
|
|
1202
|
+
or cannot be found via ``shutil.which``. Results of the
|
|
1203
|
+
``shutil.which`` lookup are cached in ``self.CMDPATHS``.
|
|
1201
1204
|
"""
|
|
1202
1205
|
if not cmdstr: return ''
|
|
1203
|
-
|
|
1204
|
-
cmd =
|
|
1205
|
-
if
|
|
1206
|
-
|
|
1207
|
-
pcmd
|
|
1208
|
-
|
|
1206
|
+
sp = cmdstr.find(' ')
|
|
1207
|
+
cmd = cmdstr if sp < 0 else cmdstr[:sp]
|
|
1208
|
+
if '/' in cmd or '\\' in cmd: return cmdstr
|
|
1209
|
+
pcmd = self.CMDPATHS.get(cmd)
|
|
1210
|
+
if pcmd is None:
|
|
1211
|
+
pcmd = shutil.which(cmd) or ''
|
|
1212
|
+
self.CMDPATHS[cmd] = pcmd
|
|
1213
|
+
if not pcmd: return cmdstr
|
|
1214
|
+
return pcmd if sp < 0 else pcmd + cmdstr[sp:]
|
|
1209
1215
|
|
|
1210
1216
|
def add_carbon_copy(self, cc=None, isstr=None, exclude=0, specialist=None):
|
|
1211
1217
|
"""Update the Cc address list in ``PGLOG['CCDADDR']``.
|
|
@@ -1595,15 +1601,12 @@ class PgLOG:
|
|
|
1595
1601
|
os.environ['MAIL'] = re.sub(self.PGLOG['CURUID'], specialist, os.environ['MAIL'])
|
|
1596
1602
|
home = "{}/{}".format(self.PGLOG['USRHOME'], specialist)
|
|
1597
1603
|
shell = "tcsh"
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
home = ms.group(1)
|
|
1605
|
-
shell = op.basename(ms.group(2))
|
|
1606
|
-
break
|
|
1604
|
+
try:
|
|
1605
|
+
pwent = pwd.getpwnam(specialist)
|
|
1606
|
+
home = pwent.pw_dir
|
|
1607
|
+
shell = op.basename(pwent.pw_shell)
|
|
1608
|
+
except KeyError:
|
|
1609
|
+
pass
|
|
1607
1610
|
if home != os.environ['HOME'] and op.exists(home):
|
|
1608
1611
|
os.environ['HOME'] = home
|
|
1609
1612
|
return shell
|
|
@@ -14,6 +14,8 @@ import sys
|
|
|
14
14
|
import errno
|
|
15
15
|
import signal
|
|
16
16
|
import time
|
|
17
|
+
import subprocess
|
|
18
|
+
import psutil
|
|
17
19
|
from contextlib import contextmanager
|
|
18
20
|
from .pg_dbi import PgDBI
|
|
19
21
|
|
|
@@ -204,6 +206,42 @@ class PgSIG(PgDBI):
|
|
|
204
206
|
self.PGLOG['LOGMASK'] |= self.MSGLOG # turn on logging before daemon stops
|
|
205
207
|
self.pglog("{} Started at {}, Stopped gracefully{} by {}".format(self.PGSIG['DSTR'], self.PGSIG['STRTM'], msg, self.current_datetime()), self.LOGWRN)
|
|
206
208
|
|
|
209
|
+
# scan running processes via psutil and return a list of dicts matching aname/uname
|
|
210
|
+
# Mirrors the semantics of the previous "ps -u U -f | grep A" / "ps -C A -f" pipelines:
|
|
211
|
+
# - with uname: substring match of aname anywhere in cmdline (looser, like grep)
|
|
212
|
+
# - without uname: aname matches the executable basename (with optional .ext)
|
|
213
|
+
# Each entry has keys: 'pid', 'ppid', 'args' ('args' is cmdline[1:] joined).
|
|
214
|
+
def _scan_app_processes(self, aname, uname=None):
|
|
215
|
+
"""Scan running processes matching an application name, optionally by user.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
aname (str): Application name (basename or substring of cmdline).
|
|
219
|
+
uname (str, optional): Username filter.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
list[dict]: Each dict has keys ``pid``, ``ppid``, ``args``.
|
|
223
|
+
"""
|
|
224
|
+
results = []
|
|
225
|
+
for proc in psutil.process_iter(['pid', 'ppid', 'username', 'cmdline']):
|
|
226
|
+
try:
|
|
227
|
+
info = proc.info
|
|
228
|
+
cmdline = info.get('cmdline') or []
|
|
229
|
+
if not cmdline: continue
|
|
230
|
+
if uname is not None and info.get('username') != uname: continue
|
|
231
|
+
if uname:
|
|
232
|
+
if not any(aname in arg for arg in cmdline): continue
|
|
233
|
+
else:
|
|
234
|
+
exe = os.path.basename(cmdline[0])
|
|
235
|
+
if exe != aname and re.sub(r'\.\w+$', '', exe) != aname: continue
|
|
236
|
+
results.append({
|
|
237
|
+
'pid': info['pid'],
|
|
238
|
+
'ppid': info['ppid'],
|
|
239
|
+
'args': ' '.join(cmdline[1:]),
|
|
240
|
+
})
|
|
241
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
242
|
+
continue
|
|
243
|
+
return results
|
|
244
|
+
|
|
207
245
|
# check if a daemon is running already
|
|
208
246
|
# aname - application name for the daemon
|
|
209
247
|
# uname - user login name who started the daemon
|
|
@@ -218,21 +256,11 @@ class PgSIG(PgDBI):
|
|
|
218
256
|
Returns:
|
|
219
257
|
int: The process ID of the running daemon, or 0 if not running.
|
|
220
258
|
"""
|
|
221
|
-
if uname:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
pcmd = "ps -C {} -f | grep ' 1 '".format(aname)
|
|
227
|
-
mp = r"^\s*\w+\s+(\d+)\s+1\s+"
|
|
228
|
-
buf = self.pgsystem(pcmd, self.LOGWRN, 20+1024)
|
|
229
|
-
if buf:
|
|
230
|
-
cpid = os.getpid()
|
|
231
|
-
lines = buf.split('\n')
|
|
232
|
-
for line in lines:
|
|
233
|
-
ms = re.match(mp, line)
|
|
234
|
-
pid = int(ms.group(1)) if ms else 0
|
|
235
|
-
if pid > 0 and pid != cpid: return pid
|
|
259
|
+
if uname: self.check_vuser(uname, aname)
|
|
260
|
+
cpid = os.getpid()
|
|
261
|
+
for p in self._scan_app_processes(aname, uname):
|
|
262
|
+
if p['ppid'] != 1: continue
|
|
263
|
+
if p['pid'] != cpid: return p['pid']
|
|
236
264
|
return 0
|
|
237
265
|
|
|
238
266
|
# check if an application is running already; other than the current processs
|
|
@@ -251,31 +279,21 @@ class PgSIG(PgDBI):
|
|
|
251
279
|
Returns:
|
|
252
280
|
int: The process ID of the running instance, or 0 if not found.
|
|
253
281
|
"""
|
|
254
|
-
if uname:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
mp = r"^\s*{}\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(uname, aname)
|
|
258
|
-
else:
|
|
259
|
-
pcmd = "ps -C {} -f".format(aname)
|
|
260
|
-
mp = r"^\s*\w+\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(aname)
|
|
261
|
-
buf = self.pgsystem(pcmd, self.LOGWRN, 20+1024)
|
|
262
|
-
if not buf: return 0
|
|
282
|
+
if uname: self.check_vuser(uname, aname)
|
|
283
|
+
procs = self._scan_app_processes(aname, uname)
|
|
284
|
+
if not procs: return 0
|
|
263
285
|
cpids = [os.getpid(), os.getppid()]
|
|
264
286
|
pids = []
|
|
265
287
|
ppids = []
|
|
266
288
|
astrs = []
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
ms = re.match(mp, line)
|
|
270
|
-
if not ms: continue
|
|
271
|
-
pid = int(ms.group(1))
|
|
272
|
-
ppid = int(ms.group(2))
|
|
289
|
+
for p in procs:
|
|
290
|
+
pid, ppid = p['pid'], p['ppid']
|
|
273
291
|
if pid in cpids:
|
|
274
292
|
if ppid not in cpids: cpids.append(ppid)
|
|
275
293
|
continue
|
|
276
294
|
pids.append(pid)
|
|
277
295
|
ppids.append(ppid)
|
|
278
|
-
if sargv: astrs.append(
|
|
296
|
+
if sargv: astrs.append(p['args'])
|
|
279
297
|
pcnt = len(pids)
|
|
280
298
|
if not pcnt: return 0
|
|
281
299
|
i = 0
|
|
@@ -329,25 +347,15 @@ class PgSIG(PgDBI):
|
|
|
329
347
|
Returns:
|
|
330
348
|
int: Number of running instances (excluding the current process).
|
|
331
349
|
"""
|
|
332
|
-
if uname:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
mp = r"^\s*{}\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(uname, aname)
|
|
336
|
-
else:
|
|
337
|
-
pcmd = "ps -C {} -f".format(aname)
|
|
338
|
-
mp = r"^\s*\w+\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(aname)
|
|
339
|
-
buf = self.pgsystem(pcmd, self.LOGWRN, 20+1024)
|
|
340
|
-
if not buf: return 0
|
|
350
|
+
if uname: self.check_vuser(uname, aname)
|
|
351
|
+
procs = self._scan_app_processes(aname, uname)
|
|
352
|
+
if not procs: return 0
|
|
341
353
|
dpids = [os.getpid(), os.getppid()]
|
|
342
354
|
pids = []
|
|
343
355
|
ppids = []
|
|
344
356
|
astrs = []
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
ms = re.match(mp, line)
|
|
348
|
-
if not ms: continue
|
|
349
|
-
pid = int(ms.group(1))
|
|
350
|
-
ppid = int(ms.group(2))
|
|
357
|
+
for p in procs:
|
|
358
|
+
pid, ppid = p['pid'], p['ppid']
|
|
351
359
|
if pid in dpids:
|
|
352
360
|
if ppid > 1 and ppid not in dpids: dpids.append(ppid)
|
|
353
361
|
continue
|
|
@@ -356,7 +364,7 @@ class PgSIG(PgDBI):
|
|
|
356
364
|
continue
|
|
357
365
|
pids.append(pid)
|
|
358
366
|
ppids.append(ppid)
|
|
359
|
-
if sargv: astrs.append(
|
|
367
|
+
if sargv: astrs.append(p['args'])
|
|
360
368
|
pcnt = len(pids)
|
|
361
369
|
if not pcnt: return 0
|
|
362
370
|
i = 0
|
|
@@ -627,18 +635,20 @@ class PgSIG(PgDBI):
|
|
|
627
635
|
list: PIDs of processes that were successfully killed.
|
|
628
636
|
"""
|
|
629
637
|
if logact is None: logact = self.LOGWRN
|
|
630
|
-
buf = self.pgsystem("ps --ppid {} -o pid".format(pid), logact, 20)
|
|
631
638
|
pids = []
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
639
|
+
try:
|
|
640
|
+
children = psutil.Process(pid).children()
|
|
641
|
+
except psutil.NoSuchProcess:
|
|
642
|
+
children = []
|
|
643
|
+
except Exception as e:
|
|
644
|
+
self.pglog("Error listing children of pid {}: {}".format(pid, str(e)), logact)
|
|
645
|
+
children = []
|
|
646
|
+
for child in children:
|
|
647
|
+
cid = child.pid
|
|
648
|
+
if not self.check_process(cid): continue
|
|
649
|
+
cids = self.kill_children(cid, logact)
|
|
650
|
+
if cids: pids = cids + pids
|
|
651
|
+
if self.kill_process(cid, signal.SIGKILL, logact) == self.SUCCESS: pids.insert(0, cid)
|
|
642
652
|
if logact and len(pids): self.pglog("Process({}) Killed".format(','.join(map(str, pids))), logact)
|
|
643
653
|
return pids
|
|
644
654
|
|
|
@@ -808,13 +818,11 @@ class PgSIG(PgDBI):
|
|
|
808
818
|
Returns:
|
|
809
819
|
int: 1 if the process is running, 0 otherwise.
|
|
810
820
|
"""
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if re.match(mp, line): return 1
|
|
817
|
-
return 0
|
|
821
|
+
try:
|
|
822
|
+
os.kill(pid, 0)
|
|
823
|
+
except OSError:
|
|
824
|
+
return 0
|
|
825
|
+
return 1
|
|
818
826
|
|
|
819
827
|
# check a process id on give host
|
|
820
828
|
def check_host_pid(self, host, pid, pmsg=None, logact=None):
|
|
@@ -1092,7 +1100,10 @@ class PgSIG(PgDBI):
|
|
|
1092
1100
|
self.PGLOG['ERRFILE'] = re.sub(r'\.log$', '.err', self.PGLOG['LOGFILE'], 1)
|
|
1093
1101
|
bckcmd += " 2>> {}/{}".format(self.PGLOG['LOGPATH'], self.PGLOG['ERRFILE'])
|
|
1094
1102
|
bckcmd += " &"
|
|
1095
|
-
|
|
1103
|
+
# shell=True is required for the redirections (>> / 2>>) and trailing '&';
|
|
1104
|
+
# the '&' makes the shell fork the command and exit, so the command gets
|
|
1105
|
+
# reparented to init (ppid=1), matching the lookup logic in record_background().
|
|
1106
|
+
subprocess.Popen(bckcmd, shell=True)
|
|
1096
1107
|
return self.record_background(cmd, logact)
|
|
1097
1108
|
|
|
1098
1109
|
# get background process id for given bcmd
|
|
@@ -1170,27 +1181,31 @@ class PgSIG(PgDBI):
|
|
|
1170
1181
|
"""
|
|
1171
1182
|
if logact is None: logact = self.LOGWRN
|
|
1172
1183
|
ms = re.match(r'^(\S+)', bcmd)
|
|
1173
|
-
if ms
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
aname = bcmd
|
|
1177
|
-
mp = r"^\s*(\S+)\s+(\d+)\s+1\s+.*{}(.*)$".format(aname)
|
|
1178
|
-
pc = "ps -u {},{} -f | grep ' 1 ' | grep {}".format(self.PGLOG['CURUID'], self.PGLOG['GDEXUSER'], aname)
|
|
1184
|
+
aname = ms.group(1) if ms else bcmd
|
|
1185
|
+
curuid = self.PGLOG['CURUID']
|
|
1186
|
+
gdexuser = self.PGLOG['GDEXUSER']
|
|
1179
1187
|
for i in range(2):
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
if
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
+
for proc in psutil.process_iter(['pid', 'ppid', 'username', 'cmdline']):
|
|
1189
|
+
try:
|
|
1190
|
+
info = proc.info
|
|
1191
|
+
if info.get('ppid') != 1: continue
|
|
1192
|
+
uid = info.get('username')
|
|
1193
|
+
if uid != curuid and uid != gdexuser: continue
|
|
1194
|
+
cmdline = info.get('cmdline') or []
|
|
1195
|
+
if not cmdline: continue
|
|
1196
|
+
line = ' '.join(cmdline)
|
|
1197
|
+
idx = line.find(aname)
|
|
1198
|
+
if idx < 0: continue
|
|
1199
|
+
bid = info['pid']
|
|
1188
1200
|
if bid in self.CBIDS: return -1
|
|
1189
|
-
|
|
1201
|
+
acmd = line[idx+len(aname):]
|
|
1202
|
+
if uid == gdexuser:
|
|
1190
1203
|
acmd = re.sub(r'^\.(pl|py)\s+', '', acmd, 1)
|
|
1191
1204
|
if re.match(r'^{}{}'.format(aname, acmd), bcmd): continue
|
|
1192
1205
|
self.CBIDS[bid] = bcmd
|
|
1193
1206
|
return 1
|
|
1207
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
1208
|
+
continue
|
|
1194
1209
|
time.sleep(2)
|
|
1195
1210
|
return 0
|
|
1196
1211
|
|
{rda_python_common-2.1.9 → rda_python_common-2.1.11/src/rda_python_common.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_common
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.11
|
|
4
4
|
Summary: RDA Python common library codes shared by other RDA python packages
|
|
5
5
|
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/NCAR/rda-python-common
|
|
@@ -11,8 +11,8 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
11
11
|
Requires-Python: >=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: psycopg2-binary
|
|
15
|
+
Requires-Dist: psutil
|
|
16
16
|
Requires-Dist: rda-python-globus
|
|
17
17
|
Requires-Dist: unidecode
|
|
18
18
|
Requires-Dist: hvac
|
|
@@ -92,7 +92,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
|
|
|
92
92
|
python -c "import rda_python_common; print(rda_python_common.__version__)"
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
You should see the installed version (currently `2.1.
|
|
95
|
+
You should see the installed version (currently `2.1.11`). If the import
|
|
96
96
|
fails, double-check that the active Python environment is the one where you
|
|
97
97
|
ran `pip install`.
|
|
98
98
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{rda_python_common-2.1.9 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|