DIRAC 9.0.0a68__py3-none-any.whl → 9.0.0a70__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.
- DIRAC/AccountingSystem/Client/Types/Network.py +8 -8
- DIRAC/AccountingSystem/Client/Types/PilotSubmission.py +3 -3
- DIRAC/ConfigurationSystem/Client/CSAPI.py +11 -1
- DIRAC/ConfigurationSystem/Client/Helpers/CSGlobals.py +0 -9
- DIRAC/ConfigurationSystem/Client/Helpers/Registry.py +3 -29
- DIRAC/ConfigurationSystem/Client/SyncPlugins/CERNLDAPSyncPlugin.py +4 -1
- DIRAC/ConfigurationSystem/ConfigTemplate.cfg +3 -0
- DIRAC/ConfigurationSystem/private/Modificator.py +11 -3
- DIRAC/ConfigurationSystem/private/RefresherBase.py +4 -2
- DIRAC/Core/DISET/ServiceReactor.py +11 -3
- DIRAC/Core/DISET/private/Transports/M2SSLTransport.py +9 -7
- DIRAC/Core/Security/DiracX.py +11 -6
- DIRAC/Core/Security/test/test_diracx_token_from_pem.py +161 -0
- DIRAC/Core/Tornado/Server/TornadoService.py +1 -1
- DIRAC/Core/Utilities/ElasticSearchDB.py +1 -2
- DIRAC/Core/Utilities/Subprocess.py +66 -57
- DIRAC/Core/Utilities/test/Test_Profiler.py +20 -20
- DIRAC/Core/Utilities/test/Test_Subprocess.py +58 -8
- DIRAC/Core/scripts/dirac_apptainer_exec.py +8 -8
- DIRAC/DataManagementSystem/Agent/FTS3Agent.py +8 -7
- DIRAC/DataManagementSystem/Client/DataManager.py +6 -7
- DIRAC/DataManagementSystem/Client/FTS3Job.py +125 -34
- DIRAC/DataManagementSystem/Client/test/Test_FTS3Objects.py +1 -0
- DIRAC/DataManagementSystem/Client/test/Test_scitag.py +69 -0
- DIRAC/DataManagementSystem/DB/FileCatalogComponents/DatasetManager/DatasetManager.py +1 -1
- DIRAC/DataManagementSystem/scripts/dirac_dms_create_moving_request.py +2 -0
- DIRAC/FrameworkSystem/DB/InstalledComponentsDB.py +3 -2
- DIRAC/FrameworkSystem/DB/ProxyDB.py +9 -5
- DIRAC/FrameworkSystem/Utilities/MonitoringUtilities.py +1 -0
- DIRAC/FrameworkSystem/Utilities/TokenManagementUtilities.py +3 -2
- DIRAC/FrameworkSystem/Utilities/diracx.py +41 -10
- DIRAC/FrameworkSystem/scripts/dirac_login.py +2 -2
- DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +1 -1
- DIRAC/FrameworkSystem/scripts/dirac_uninstall_component.py +1 -0
- DIRAC/Interfaces/API/Dirac.py +3 -6
- DIRAC/Interfaces/Utilities/DConfigCache.py +2 -0
- DIRAC/Interfaces/scripts/dirac_wms_job_parameters.py +0 -1
- DIRAC/MonitoringSystem/DB/MonitoringDB.py +6 -5
- DIRAC/MonitoringSystem/Service/WebAppHandler.py +25 -6
- DIRAC/MonitoringSystem/private/MainReporter.py +0 -3
- DIRAC/RequestManagementSystem/Agent/RequestExecutingAgent.py +8 -6
- DIRAC/RequestManagementSystem/ConfigTemplate.cfg +6 -6
- DIRAC/ResourceStatusSystem/Command/FreeDiskSpaceCommand.py +3 -1
- DIRAC/Resources/Computing/AREXComputingElement.py +18 -2
- DIRAC/Resources/Computing/BatchSystems/Condor.py +0 -3
- DIRAC/Resources/Computing/BatchSystems/executeBatch.py +15 -7
- DIRAC/Resources/Computing/LocalComputingElement.py +0 -2
- DIRAC/Resources/Computing/SSHComputingElement.py +61 -38
- DIRAC/Resources/IdProvider/CheckInIdProvider.py +13 -0
- DIRAC/Resources/IdProvider/IdProviderFactory.py +13 -3
- DIRAC/Resources/IdProvider/tests/Test_IdProviderFactory.py +7 -0
- DIRAC/Resources/Storage/FileStorage.py +121 -2
- DIRAC/TransformationSystem/Agent/InputDataAgent.py +4 -1
- DIRAC/TransformationSystem/Agent/MCExtensionAgent.py +5 -2
- DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +3 -4
- DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +44 -9
- DIRAC/TransformationSystem/Agent/ValidateOutputDataAgent.py +4 -2
- DIRAC/TransformationSystem/Client/TransformationClient.py +9 -1
- DIRAC/TransformationSystem/Client/Utilities.py +6 -3
- DIRAC/TransformationSystem/DB/TransformationDB.py +105 -43
- DIRAC/TransformationSystem/Utilities/ReplicationCLIParameters.py +3 -3
- DIRAC/TransformationSystem/scripts/dirac_production_runjoblocal.py +2 -4
- DIRAC/TransformationSystem/test/Test_replicationTransformation.py +5 -6
- DIRAC/WorkloadManagementSystem/Agent/JobAgent.py +1 -5
- DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py +4 -3
- DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py +0 -4
- DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +8 -11
- DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +39 -7
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_SiteDirector.py +8 -2
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +24 -4
- DIRAC/WorkloadManagementSystem/Client/DownloadInputData.py +4 -3
- DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg +3 -3
- DIRAC/WorkloadManagementSystem/DB/JobParametersDB.py +8 -8
- DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +1 -1
- DIRAC/WorkloadManagementSystem/DB/StatusUtils.py +48 -21
- DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py +19 -4
- DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +3 -4
- DIRAC/WorkloadManagementSystem/JobWrapper/Watchdog.py +16 -45
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +18 -9
- DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +25 -2
- DIRAC/WorkloadManagementSystem/Service/WMSAdministratorHandler.py +18 -31
- DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py +73 -7
- {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/METADATA +6 -5
- {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/RECORD +88 -86
- {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/WHEEL +0 -0
- {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/entry_points.txt +0 -0
- {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/licenses/LICENSE +0 -0
- {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
DIRAC Wrapper to execute python and system commands with a wrapper, that might
|
|
3
3
|
set a timeout.
|
|
4
|
-
3
|
|
4
|
+
3 functions are provided:
|
|
5
5
|
|
|
6
6
|
- shellCall( iTimeOut, cmdSeq, callbackFunction = None, env = None ):
|
|
7
7
|
it uses subprocess.Popen class with "shell = True".
|
|
@@ -26,6 +26,7 @@ set a timeout.
|
|
|
26
26
|
should be used to wrap third party python functions
|
|
27
27
|
|
|
28
28
|
"""
|
|
29
|
+
|
|
29
30
|
import os
|
|
30
31
|
import selectors
|
|
31
32
|
import signal
|
|
@@ -161,7 +162,6 @@ class Subprocess:
|
|
|
161
162
|
|
|
162
163
|
self.child = None
|
|
163
164
|
self.childPID = 0
|
|
164
|
-
self.childKilled = False
|
|
165
165
|
self.callback = None
|
|
166
166
|
self.bufferList = []
|
|
167
167
|
self.cmdSeq = []
|
|
@@ -193,7 +193,7 @@ class Subprocess:
|
|
|
193
193
|
f"First and last data in buffer: \n{dataString[:100]} \n....\n {dataString[-100:]} ",
|
|
194
194
|
)
|
|
195
195
|
retDict = S_ERROR(
|
|
196
|
-
"Reached maximum allowed length (%d bytes)
|
|
196
|
+
"Reached maximum allowed length (%d bytes) for called function return value" % self.bufferLimit
|
|
197
197
|
)
|
|
198
198
|
retDict["Value"] = dataString
|
|
199
199
|
return retDict
|
|
@@ -241,60 +241,71 @@ class Subprocess:
|
|
|
241
241
|
events = sel.select(timeout=timeout or self.timeout or None)
|
|
242
242
|
return [key.fileobj for key, event in events if event & selectors.EVENT_READ]
|
|
243
243
|
|
|
244
|
-
def
|
|
245
|
-
"""
|
|
246
|
-
|
|
247
|
-
:param int pid: process id
|
|
248
|
-
:param int sig: signal to send, default 9 (SIGKILL)
|
|
249
|
-
"""
|
|
244
|
+
def __terminateProcess(self, process):
|
|
245
|
+
"""Tries to terminate a process with SIGTERM. Returns a (gone, alive) tuple"""
|
|
246
|
+
self.log.verbose(f"Sending SIGTERM signal to PID {process.pid}")
|
|
250
247
|
try:
|
|
251
|
-
|
|
252
|
-
except
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
248
|
+
process.terminate()
|
|
249
|
+
except psutil.NoSuchProcess:
|
|
250
|
+
return ([], [])
|
|
251
|
+
return psutil.wait_procs([process], timeout=60)
|
|
252
|
+
|
|
253
|
+
def __poll(self, process):
|
|
254
|
+
"""Non-blocking check of whether process `pid` is still alive.
|
|
255
|
+
Returns:
|
|
256
|
+
- (0, 0) if process is still running (like os.waitpid(pid, os.WNOHANG))
|
|
257
|
+
- (pid, exitcode) if process has terminated
|
|
258
|
+
- None if process info cannot be retrieved
|
|
259
|
+
"""
|
|
259
260
|
try:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
exitcode = process.wait(timeout=0)
|
|
262
|
+
return (process.pid, exitcode) # exited
|
|
263
|
+
except psutil.TimeoutExpired:
|
|
264
|
+
return (0, 0) # still running
|
|
265
|
+
except psutil.NoSuchProcess:
|
|
264
266
|
return None
|
|
265
267
|
|
|
266
268
|
def killChild(self, recursive=True):
|
|
267
|
-
"""
|
|
268
|
-
|
|
269
|
-
:param boolean recursive: flag to kill all descendants
|
|
269
|
+
"""Kills a process tree (including children) with signal SIGTERM. If that fails, escalate to SIGKILL
|
|
270
|
+
returns (gone, alive) tuple.
|
|
270
271
|
"""
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
272
|
+
|
|
273
|
+
self.log.info(f"Killing childPID {self.childPID}")
|
|
274
|
+
|
|
275
|
+
gone, alive = [], []
|
|
276
|
+
try:
|
|
277
|
+
child_process = psutil.Process(self.childPID)
|
|
278
|
+
except psutil.NoSuchProcess:
|
|
279
|
+
self.log.warn(f"Child PID {self.childPID} no longer exists")
|
|
280
|
+
return (gone, alive)
|
|
281
|
+
|
|
282
|
+
if recursive:
|
|
283
|
+
# grandchildren
|
|
284
|
+
children = child_process.children(recursive=True)
|
|
285
|
+
self.log.info(f"Sending kill signal to {len(children)} children PIDs")
|
|
286
|
+
for p in children:
|
|
285
287
|
try:
|
|
286
288
|
p.terminate()
|
|
287
289
|
except psutil.NoSuchProcess:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
continue
|
|
291
|
+
g, a = psutil.wait_procs(children, timeout=60)
|
|
292
|
+
gone.extend(g)
|
|
293
|
+
alive.extend(a)
|
|
294
|
+
|
|
295
|
+
# now killing the child_process
|
|
296
|
+
g, a = self.__terminateProcess(child_process)
|
|
297
|
+
gone.extend(g)
|
|
298
|
+
alive.extend(a)
|
|
299
|
+
|
|
300
|
+
# if there's something still alive, use SIGKILL
|
|
301
|
+
if alive:
|
|
291
302
|
for p in alive:
|
|
292
303
|
try:
|
|
293
304
|
p.kill()
|
|
294
305
|
except psutil.NoSuchProcess:
|
|
295
306
|
pass
|
|
296
307
|
|
|
297
|
-
|
|
308
|
+
return psutil.wait_procs(alive, timeout=60)
|
|
298
309
|
|
|
299
310
|
def pythonCall(self, function, *stArgs, **stKeyArgs):
|
|
300
311
|
"""call python function :function: with :stArgs: and :stKeyArgs:"""
|
|
@@ -309,8 +320,6 @@ class Subprocess:
|
|
|
309
320
|
if pid == 0:
|
|
310
321
|
os.close(readFD)
|
|
311
322
|
self.__executePythonFunction(function, writeFD, *stArgs, **stKeyArgs)
|
|
312
|
-
# FIXME: the close it is done at __executePythonFunction, do we need it here?
|
|
313
|
-
os.close(writeFD)
|
|
314
323
|
else:
|
|
315
324
|
os.close(writeFD)
|
|
316
325
|
readSeq = self.__selectFD([readFD])
|
|
@@ -319,14 +328,13 @@ class Subprocess:
|
|
|
319
328
|
try:
|
|
320
329
|
if len(readSeq) == 0:
|
|
321
330
|
self.log.debug("Timeout limit reached for pythonCall", function.__name__)
|
|
322
|
-
self.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
retries -= 1
|
|
331
|
+
gone, alive = self.__terminateProcess(psutil.Process(pid))
|
|
332
|
+
if alive:
|
|
333
|
+
for p in alive:
|
|
334
|
+
try:
|
|
335
|
+
p.kill()
|
|
336
|
+
except psutil.NoSuchProcess:
|
|
337
|
+
continue
|
|
330
338
|
|
|
331
339
|
return S_ERROR('%d seconds timeout for "%s" call' % (self.timeout, function.__name__))
|
|
332
340
|
elif readSeq[0] == readFD:
|
|
@@ -400,7 +408,7 @@ class Subprocess:
|
|
|
400
408
|
if len(dataString) + baseLength > self.bufferLimit:
|
|
401
409
|
self.log.error("Maximum output buffer length reached")
|
|
402
410
|
retDict = S_ERROR(
|
|
403
|
-
"Reached maximum allowed length (%d bytes) for called
|
|
411
|
+
"Reached maximum allowed length (%d bytes) for called function return value" % self.bufferLimit
|
|
404
412
|
)
|
|
405
413
|
retDict["Value"] = dataString
|
|
406
414
|
return retDict
|
|
@@ -446,6 +454,7 @@ class Subprocess:
|
|
|
446
454
|
start_new_session=start_new_session,
|
|
447
455
|
)
|
|
448
456
|
self.childPID = self.child.pid
|
|
457
|
+
child_process = psutil.Process(self.childPID)
|
|
449
458
|
except OSError as v:
|
|
450
459
|
retDict = S_ERROR(repr(v))
|
|
451
460
|
retDict["Value"] = (-1, "", str(v))
|
|
@@ -464,9 +473,9 @@ class Subprocess:
|
|
|
464
473
|
self.bufferList = [["", 0], ["", 0]]
|
|
465
474
|
initialTime = time.time()
|
|
466
475
|
|
|
467
|
-
exitStatus = self.__poll(
|
|
476
|
+
exitStatus = self.__poll(child_process)
|
|
468
477
|
|
|
469
|
-
while (0, 0) == exitStatus
|
|
478
|
+
while (0, 0) == exitStatus: # This means that the process is still alive
|
|
470
479
|
retDict = self.__readFromCommand()
|
|
471
480
|
if not retDict["OK"]:
|
|
472
481
|
return retDict
|
|
@@ -478,14 +487,14 @@ class Subprocess:
|
|
|
478
487
|
1, "Timeout (%d seconds) for '%s' call" % (self.timeout, cmdSeq)
|
|
479
488
|
)
|
|
480
489
|
time.sleep(0.01)
|
|
481
|
-
exitStatus = self.__poll(
|
|
490
|
+
exitStatus = self.__poll(child_process)
|
|
482
491
|
|
|
483
492
|
self.__readFromCommand()
|
|
484
493
|
|
|
485
494
|
if exitStatus:
|
|
486
495
|
exitStatus = exitStatus[1]
|
|
487
496
|
|
|
488
|
-
if exitStatus >= 256:
|
|
497
|
+
if exitStatus and exitStatus >= 256:
|
|
489
498
|
exitStatus = int(exitStatus / 256)
|
|
490
499
|
return S_OK((exitStatus, self.bufferList[0][0], self.bufferList[1][0]))
|
|
491
500
|
finally:
|
|
@@ -29,50 +29,50 @@ def test_base():
|
|
|
29
29
|
time.sleep(1)
|
|
30
30
|
p = Profiler(mainProcess.pid)
|
|
31
31
|
res = p.pid()
|
|
32
|
-
assert res["OK"] is True
|
|
32
|
+
assert res["OK"] is True, res
|
|
33
33
|
res = p.status()
|
|
34
|
-
assert res["OK"] is True
|
|
34
|
+
assert res["OK"] is True, res
|
|
35
35
|
res = p.runningTime()
|
|
36
|
-
assert res["OK"] is True
|
|
36
|
+
assert res["OK"] is True, res
|
|
37
37
|
assert res["Value"] > 0
|
|
38
38
|
|
|
39
39
|
res = p.memoryUsage()
|
|
40
|
-
assert res["OK"] is True
|
|
40
|
+
assert res["OK"] is True, res
|
|
41
41
|
assert res["Value"] > 0
|
|
42
42
|
resWC = p.memoryUsage(withChildren=True)
|
|
43
|
-
assert resWC["OK"] is True
|
|
43
|
+
assert resWC["OK"] is True, res
|
|
44
44
|
assert resWC["Value"] > 0
|
|
45
45
|
assert resWC["Value"] >= res["Value"]
|
|
46
46
|
|
|
47
47
|
res = p.vSizeUsage()
|
|
48
|
-
assert res["OK"] is True
|
|
48
|
+
assert res["OK"] is True, res
|
|
49
49
|
assert res["Value"] > 0
|
|
50
50
|
resWC = p.vSizeUsage(withChildren=True)
|
|
51
|
-
assert resWC["OK"] is True
|
|
51
|
+
assert resWC["OK"] is True, res
|
|
52
52
|
assert resWC["Value"] > 0
|
|
53
53
|
assert resWC["Value"] >= res["Value"]
|
|
54
54
|
|
|
55
55
|
res = p.vSizeUsage()
|
|
56
|
-
assert res["OK"] is True
|
|
56
|
+
assert res["OK"] is True, res
|
|
57
57
|
assert res["Value"] > 0
|
|
58
58
|
resWC = p.vSizeUsage(withChildren=True)
|
|
59
|
-
assert resWC["OK"] is True
|
|
59
|
+
assert resWC["OK"] is True, res
|
|
60
60
|
assert resWC["Value"] > 0
|
|
61
61
|
assert resWC["Value"] >= res["Value"]
|
|
62
62
|
|
|
63
63
|
res = p.numThreads()
|
|
64
|
-
assert res["OK"] is True
|
|
64
|
+
assert res["OK"] is True, res
|
|
65
65
|
assert res["Value"] > 0
|
|
66
66
|
resWC = p.numThreads(withChildren=True)
|
|
67
|
-
assert resWC["OK"] is True
|
|
67
|
+
assert resWC["OK"] is True, res
|
|
68
68
|
assert resWC["Value"] > 0
|
|
69
69
|
assert resWC["Value"] >= res["Value"]
|
|
70
70
|
|
|
71
71
|
res = p.cpuPercentage()
|
|
72
|
-
assert res["OK"] is True
|
|
72
|
+
assert res["OK"] is True, res
|
|
73
73
|
assert res["Value"] >= 0
|
|
74
74
|
resWC = p.cpuPercentage(withChildren=True)
|
|
75
|
-
assert resWC["OK"] is True
|
|
75
|
+
assert resWC["OK"] is True, res
|
|
76
76
|
assert resWC["Value"] >= 0
|
|
77
77
|
assert resWC["Value"] >= res["Value"]
|
|
78
78
|
|
|
@@ -88,13 +88,13 @@ def test_cpuUsage():
|
|
|
88
88
|
time.sleep(2)
|
|
89
89
|
p = Profiler(mainProcess.pid)
|
|
90
90
|
res = p.pid()
|
|
91
|
-
assert res["OK"] is True
|
|
91
|
+
assert res["OK"] is True, res
|
|
92
92
|
res = p.status()
|
|
93
|
-
assert res["OK"] is True
|
|
93
|
+
assert res["OK"] is True, res
|
|
94
94
|
|
|
95
95
|
# user
|
|
96
96
|
res = p.cpuUsageUser()
|
|
97
|
-
assert res["OK"] is True
|
|
97
|
+
assert res["OK"] is True, res
|
|
98
98
|
assert res["Value"] > 0
|
|
99
99
|
resC = p.cpuUsageUser(withChildren=True)
|
|
100
100
|
assert resC["OK"] is True
|
|
@@ -102,7 +102,7 @@ def test_cpuUsage():
|
|
|
102
102
|
assert resC["Value"] >= res["Value"]
|
|
103
103
|
|
|
104
104
|
res = p.cpuUsageUser()
|
|
105
|
-
assert res["OK"] is True
|
|
105
|
+
assert res["OK"] is True, res
|
|
106
106
|
assert res["Value"] > 0
|
|
107
107
|
resC = p.cpuUsageUser(withChildren=True)
|
|
108
108
|
assert resC["OK"] is True
|
|
@@ -121,15 +121,15 @@ def test_cpuUsage():
|
|
|
121
121
|
|
|
122
122
|
# system
|
|
123
123
|
res = p.cpuUsageSystem()
|
|
124
|
-
assert res["OK"] is True
|
|
124
|
+
assert res["OK"] is True, res
|
|
125
125
|
assert res["Value"] >= 0
|
|
126
126
|
resWC = p.cpuUsageSystem(withChildren=True)
|
|
127
|
-
assert resWC["OK"] is True
|
|
127
|
+
assert resWC["OK"] is True, res
|
|
128
128
|
assert resWC["Value"] >= 0
|
|
129
129
|
assert resWC["Value"] >= res["Value"]
|
|
130
130
|
|
|
131
131
|
res = p.cpuUsageSystem()
|
|
132
|
-
assert res["OK"] is True
|
|
132
|
+
assert res["OK"] is True, res
|
|
133
133
|
assert res["Value"] > 0
|
|
134
134
|
resC = p.cpuUsageSystem(withChildren=True)
|
|
135
135
|
assert resC["OK"] is True
|
|
@@ -3,23 +3,25 @@
|
|
|
3
3
|
# Date: 2012/12/11 18:04:25
|
|
4
4
|
########################################################################
|
|
5
5
|
|
|
6
|
-
"""
|
|
7
|
-
|
|
6
|
+
""":mod: SubprocessTests
|
|
7
|
+
=======================
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
.. module: SubprocessTests
|
|
10
|
+
:synopsis: unittest for Subprocess module
|
|
11
|
+
.. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
unittest for Subprocess module
|
|
14
14
|
"""
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
import platform
|
|
17
|
+
import time
|
|
17
18
|
from os.path import dirname, join
|
|
18
19
|
from subprocess import Popen
|
|
19
20
|
|
|
21
|
+
import psutil
|
|
20
22
|
import pytest
|
|
21
23
|
|
|
22
|
-
from DIRAC.Core.Utilities.Subprocess import
|
|
24
|
+
from DIRAC.Core.Utilities.Subprocess import Subprocess, getChildrenPIDs, pythonCall, shellCall, systemCall
|
|
23
25
|
|
|
24
26
|
# Mark this entire module as slow
|
|
25
27
|
pytestmark = pytest.mark.slow
|
|
@@ -72,3 +74,51 @@ def test_decodingCommandOutput():
|
|
|
72
74
|
retVal = sp.systemCall(r"""python -c 'import os; os.fdopen(2, "wb").write(b"\xdf")'""", shell=True)
|
|
73
75
|
assert retVal["OK"]
|
|
74
76
|
assert retVal["Value"] == (0, "", "\ufffd")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.fixture
|
|
80
|
+
def subprocess_instance():
|
|
81
|
+
"""Provides a Subprocess instance for testing."""
|
|
82
|
+
subp = Subprocess()
|
|
83
|
+
return subp
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.fixture
|
|
87
|
+
def dummy_child():
|
|
88
|
+
"""Spawn a dummy process tree: parent -> child."""
|
|
89
|
+
# Start a shell that sleeps, with a subprocess child
|
|
90
|
+
parent = Popen(["bash", "-c", "sleep 10 & wait"])
|
|
91
|
+
time.sleep(0.2) # give it a moment to start
|
|
92
|
+
yield parent
|
|
93
|
+
# Ensure cleanup
|
|
94
|
+
try:
|
|
95
|
+
parent.terminate()
|
|
96
|
+
parent.wait(timeout=1)
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_kill_child_process_tree(subprocess_instance, dummy_child):
|
|
102
|
+
"""Test that killChild kills both parent and its children."""
|
|
103
|
+
subprocess_instance.childPID = dummy_child.pid
|
|
104
|
+
parent_proc = psutil.Process(subprocess_instance.childPID)
|
|
105
|
+
|
|
106
|
+
# Sanity check: parent should exist
|
|
107
|
+
assert parent_proc.is_running()
|
|
108
|
+
|
|
109
|
+
# It should have at least one sleeping child
|
|
110
|
+
children = parent_proc.children(recursive=True)
|
|
111
|
+
assert children, "Expected dummy process to have at least one child"
|
|
112
|
+
|
|
113
|
+
# Kill the tree
|
|
114
|
+
gone, alive = subprocess_instance.killChild(recursive=True)
|
|
115
|
+
|
|
116
|
+
# Verify the parent and children are terminated
|
|
117
|
+
for p in gone:
|
|
118
|
+
assert not p.is_running(), f"Process {p.pid} still alive"
|
|
119
|
+
for p in alive:
|
|
120
|
+
assert not p.is_running(), f"Process {p.pid} still alive"
|
|
121
|
+
|
|
122
|
+
# Verify parent is gone
|
|
123
|
+
with pytest.raises(psutil.NoSuchProcess):
|
|
124
|
+
psutil.Process(subprocess_instance.childPID)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
"""
|
|
3
|
-
"""
|
|
2
|
+
"""Starts a DIRAC command inside an apptainer container."""
|
|
4
3
|
|
|
5
4
|
import os
|
|
6
5
|
import sys
|
|
@@ -52,8 +51,9 @@ def main():
|
|
|
52
51
|
if switch[0].lower() == "i" or switch[0].lower() == "image":
|
|
53
52
|
user_image = switch[1]
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
cwd = os.path.realpath(os.getcwd())
|
|
55
|
+
dirac_env_var = os.environ.get("DIRAC", cwd)
|
|
56
|
+
diracos_env_var = os.environ.get("DIRACOS", cwd)
|
|
57
57
|
etc_dir = os.path.join(DIRAC.rootPath, "etc")
|
|
58
58
|
rc_script = os.path.join(os.path.realpath(sys.base_prefix), "diracosrc")
|
|
59
59
|
|
|
@@ -74,7 +74,7 @@ def main():
|
|
|
74
74
|
cmd.extend(["--contain"]) # use minimal /dev and empty other directories (e.g. /tmp and $HOME)
|
|
75
75
|
cmd.extend(["--ipc"]) # run container in a new IPC namespace
|
|
76
76
|
cmd.extend(["--pid"]) # run container in a new PID namespace
|
|
77
|
-
cmd.extend(["--bind",
|
|
77
|
+
cmd.extend(["--bind", cwd]) # bind current directory for dirac_container.sh
|
|
78
78
|
if proxy_location:
|
|
79
79
|
cmd.extend(["--bind", f"{proxy_location}:/etc/proxy"]) # bind proxy file
|
|
80
80
|
cmd.extend(["--bind", f"{getCAsLocation()}:/etc/grid-security/certificates"]) # X509_CERT_DIR
|
|
@@ -89,13 +89,13 @@ def main():
|
|
|
89
89
|
if safe_listdir(bind_path):
|
|
90
90
|
cmd.extend(["--bind", f"{bind_path}:{bind_path}"])
|
|
91
91
|
else:
|
|
92
|
-
gLogger.
|
|
93
|
-
cmd.extend(["--cwd",
|
|
92
|
+
gLogger.warn(f"Bind path {bind_path} does not exist, skipping")
|
|
93
|
+
cmd.extend(["--cwd", cwd]) # set working directory
|
|
94
94
|
|
|
95
95
|
rootImage = user_image or gConfig.getValue("/Resources/Computing/Singularity/ContainerRoot") or CONTAINER_DEFROOT
|
|
96
96
|
|
|
97
97
|
if os.path.isdir(rootImage) or os.path.isfile(rootImage):
|
|
98
|
-
cmd.extend([rootImage, f"{
|
|
98
|
+
cmd.extend([rootImage, f"{cwd}/dirac_container.sh"])
|
|
99
99
|
else:
|
|
100
100
|
# if we are here is because there's no image, or it is not accessible (e.g. not on CVMFS)
|
|
101
101
|
gLogger.error("Apptainer image to exec not found: ", rootImage)
|
|
@@ -719,10 +719,11 @@ class FTS3Agent(AgentModule):
|
|
|
719
719
|
return self.dataOpSender.concludeSending()
|
|
720
720
|
|
|
721
721
|
def __sendAccounting(self, ftsJob):
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
722
|
+
for accountingDict in ftsJob.accountingDicts:
|
|
723
|
+
self.dataOpSender.sendData(
|
|
724
|
+
accountingDict,
|
|
725
|
+
commitFlag=True,
|
|
726
|
+
delayedCommit=True,
|
|
727
|
+
startTime=fromString(ftsJob.submitTime),
|
|
728
|
+
endTime=fromString(ftsJob.lastUpdate),
|
|
729
|
+
)
|
|
@@ -10,11 +10,11 @@ This module consists of DataManager and related classes.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
# # imports
|
|
13
|
-
|
|
13
|
+
import errno
|
|
14
14
|
import fnmatch
|
|
15
15
|
import os
|
|
16
16
|
import time
|
|
17
|
-
import
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
18
|
|
|
19
19
|
# # from DIRAC
|
|
20
20
|
import DIRAC
|
|
@@ -25,13 +25,13 @@ from DIRAC.Core.Utilities.File import makeGuid, getSize
|
|
|
25
25
|
from DIRAC.Core.Utilities.List import randomize, breakListIntoChunks
|
|
26
26
|
from DIRAC.Core.Utilities.ReturnValues import returnSingleResult
|
|
27
27
|
from DIRAC.Core.Security.ProxyInfo import getProxyInfo
|
|
28
|
+
from DIRAC.Core.Security.ProxyInfo import getVOfromProxyGroup
|
|
28
29
|
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
|
|
29
30
|
from DIRAC.DataManagementSystem.Client import MAX_FILENAME_LENGTH
|
|
30
|
-
from DIRAC.MonitoringSystem.Client.DataOperationSender import DataOperationSender
|
|
31
31
|
from DIRAC.DataManagementSystem.Utilities.DMSHelpers import DMSHelpers
|
|
32
|
+
from DIRAC.MonitoringSystem.Client.DataOperationSender import DataOperationSender
|
|
32
33
|
from DIRAC.Resources.Catalog.FileCatalog import FileCatalog
|
|
33
34
|
from DIRAC.Resources.Storage.StorageElement import StorageElement
|
|
34
|
-
from DIRAC.ResourceStatusSystem.Client.ResourceStatus import ResourceStatus
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
# # RSCID
|
|
@@ -89,7 +89,7 @@ class DataManager:
|
|
|
89
89
|
:param vo: the VO for which the DataManager is created, get VO from the current proxy if not specified
|
|
90
90
|
"""
|
|
91
91
|
self.log = gLogger.getSubLogger(self.__class__.__name__)
|
|
92
|
-
self.voName = vo
|
|
92
|
+
self.voName = vo if vo else getVOfromProxyGroup().get("Value", None)
|
|
93
93
|
|
|
94
94
|
if catalogs is None:
|
|
95
95
|
catalogs = []
|
|
@@ -97,10 +97,9 @@ class DataManager:
|
|
|
97
97
|
|
|
98
98
|
self.fileCatalog = FileCatalog(catalogs=catalogsToUse, vo=self.voName)
|
|
99
99
|
self.accountingClient = None
|
|
100
|
-
self.resourceStatus = ResourceStatus()
|
|
101
100
|
self.ignoreMissingInFC = Operations(vo=self.voName).getValue("DataManagement/IgnoreMissingInFC", False)
|
|
102
101
|
self.useCatalogPFN = Operations(vo=self.voName).getValue("DataManagement/UseCatalogPFN", True)
|
|
103
|
-
self.dmsHelper = DMSHelpers(vo=
|
|
102
|
+
self.dmsHelper = DMSHelpers(vo=self.voName)
|
|
104
103
|
self.registrationProtocol = self.dmsHelper.getRegistrationProtocols()
|
|
105
104
|
self.thirdPartyProtocols = self.dmsHelper.getThirdPartyProtocols()
|
|
106
105
|
self.dataOpSender = DataOperationSender()
|