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.
Files changed (88) hide show
  1. DIRAC/AccountingSystem/Client/Types/Network.py +8 -8
  2. DIRAC/AccountingSystem/Client/Types/PilotSubmission.py +3 -3
  3. DIRAC/ConfigurationSystem/Client/CSAPI.py +11 -1
  4. DIRAC/ConfigurationSystem/Client/Helpers/CSGlobals.py +0 -9
  5. DIRAC/ConfigurationSystem/Client/Helpers/Registry.py +3 -29
  6. DIRAC/ConfigurationSystem/Client/SyncPlugins/CERNLDAPSyncPlugin.py +4 -1
  7. DIRAC/ConfigurationSystem/ConfigTemplate.cfg +3 -0
  8. DIRAC/ConfigurationSystem/private/Modificator.py +11 -3
  9. DIRAC/ConfigurationSystem/private/RefresherBase.py +4 -2
  10. DIRAC/Core/DISET/ServiceReactor.py +11 -3
  11. DIRAC/Core/DISET/private/Transports/M2SSLTransport.py +9 -7
  12. DIRAC/Core/Security/DiracX.py +11 -6
  13. DIRAC/Core/Security/test/test_diracx_token_from_pem.py +161 -0
  14. DIRAC/Core/Tornado/Server/TornadoService.py +1 -1
  15. DIRAC/Core/Utilities/ElasticSearchDB.py +1 -2
  16. DIRAC/Core/Utilities/Subprocess.py +66 -57
  17. DIRAC/Core/Utilities/test/Test_Profiler.py +20 -20
  18. DIRAC/Core/Utilities/test/Test_Subprocess.py +58 -8
  19. DIRAC/Core/scripts/dirac_apptainer_exec.py +8 -8
  20. DIRAC/DataManagementSystem/Agent/FTS3Agent.py +8 -7
  21. DIRAC/DataManagementSystem/Client/DataManager.py +6 -7
  22. DIRAC/DataManagementSystem/Client/FTS3Job.py +125 -34
  23. DIRAC/DataManagementSystem/Client/test/Test_FTS3Objects.py +1 -0
  24. DIRAC/DataManagementSystem/Client/test/Test_scitag.py +69 -0
  25. DIRAC/DataManagementSystem/DB/FileCatalogComponents/DatasetManager/DatasetManager.py +1 -1
  26. DIRAC/DataManagementSystem/scripts/dirac_dms_create_moving_request.py +2 -0
  27. DIRAC/FrameworkSystem/DB/InstalledComponentsDB.py +3 -2
  28. DIRAC/FrameworkSystem/DB/ProxyDB.py +9 -5
  29. DIRAC/FrameworkSystem/Utilities/MonitoringUtilities.py +1 -0
  30. DIRAC/FrameworkSystem/Utilities/TokenManagementUtilities.py +3 -2
  31. DIRAC/FrameworkSystem/Utilities/diracx.py +41 -10
  32. DIRAC/FrameworkSystem/scripts/dirac_login.py +2 -2
  33. DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +1 -1
  34. DIRAC/FrameworkSystem/scripts/dirac_uninstall_component.py +1 -0
  35. DIRAC/Interfaces/API/Dirac.py +3 -6
  36. DIRAC/Interfaces/Utilities/DConfigCache.py +2 -0
  37. DIRAC/Interfaces/scripts/dirac_wms_job_parameters.py +0 -1
  38. DIRAC/MonitoringSystem/DB/MonitoringDB.py +6 -5
  39. DIRAC/MonitoringSystem/Service/WebAppHandler.py +25 -6
  40. DIRAC/MonitoringSystem/private/MainReporter.py +0 -3
  41. DIRAC/RequestManagementSystem/Agent/RequestExecutingAgent.py +8 -6
  42. DIRAC/RequestManagementSystem/ConfigTemplate.cfg +6 -6
  43. DIRAC/ResourceStatusSystem/Command/FreeDiskSpaceCommand.py +3 -1
  44. DIRAC/Resources/Computing/AREXComputingElement.py +18 -2
  45. DIRAC/Resources/Computing/BatchSystems/Condor.py +0 -3
  46. DIRAC/Resources/Computing/BatchSystems/executeBatch.py +15 -7
  47. DIRAC/Resources/Computing/LocalComputingElement.py +0 -2
  48. DIRAC/Resources/Computing/SSHComputingElement.py +61 -38
  49. DIRAC/Resources/IdProvider/CheckInIdProvider.py +13 -0
  50. DIRAC/Resources/IdProvider/IdProviderFactory.py +13 -3
  51. DIRAC/Resources/IdProvider/tests/Test_IdProviderFactory.py +7 -0
  52. DIRAC/Resources/Storage/FileStorage.py +121 -2
  53. DIRAC/TransformationSystem/Agent/InputDataAgent.py +4 -1
  54. DIRAC/TransformationSystem/Agent/MCExtensionAgent.py +5 -2
  55. DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +3 -4
  56. DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +44 -9
  57. DIRAC/TransformationSystem/Agent/ValidateOutputDataAgent.py +4 -2
  58. DIRAC/TransformationSystem/Client/TransformationClient.py +9 -1
  59. DIRAC/TransformationSystem/Client/Utilities.py +6 -3
  60. DIRAC/TransformationSystem/DB/TransformationDB.py +105 -43
  61. DIRAC/TransformationSystem/Utilities/ReplicationCLIParameters.py +3 -3
  62. DIRAC/TransformationSystem/scripts/dirac_production_runjoblocal.py +2 -4
  63. DIRAC/TransformationSystem/test/Test_replicationTransformation.py +5 -6
  64. DIRAC/WorkloadManagementSystem/Agent/JobAgent.py +1 -5
  65. DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py +4 -3
  66. DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py +0 -4
  67. DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +8 -11
  68. DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +39 -7
  69. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_SiteDirector.py +8 -2
  70. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +24 -4
  71. DIRAC/WorkloadManagementSystem/Client/DownloadInputData.py +4 -3
  72. DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg +3 -3
  73. DIRAC/WorkloadManagementSystem/DB/JobParametersDB.py +8 -8
  74. DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +1 -1
  75. DIRAC/WorkloadManagementSystem/DB/StatusUtils.py +48 -21
  76. DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py +19 -4
  77. DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +3 -4
  78. DIRAC/WorkloadManagementSystem/JobWrapper/Watchdog.py +16 -45
  79. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +18 -9
  80. DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +25 -2
  81. DIRAC/WorkloadManagementSystem/Service/WMSAdministratorHandler.py +18 -31
  82. DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py +73 -7
  83. {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/METADATA +6 -5
  84. {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/RECORD +88 -86
  85. {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/WHEEL +0 -0
  86. {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/entry_points.txt +0 -0
  87. {dirac-9.0.0a68.dist-info → dirac-9.0.0a70.dist-info}/licenses/LICENSE +0 -0
  88. {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 FUNCTIONS are provided:
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) " "for called function return value" % self.bufferLimit
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 __killPid(self, pid, sig=9):
245
- """send signal :sig: to process :pid:
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
- os.kill(pid, sig)
252
- except Exception as x:
253
- if str(x) != "[Errno 3] No such process":
254
- self.log.exception("Exception while killing timed out process")
255
- raise x
256
-
257
- def __poll(self, pid):
258
- """wait for :pid:"""
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
- return os.waitpid(pid, os.WNOHANG)
261
- except os.error:
262
- if self.childKilled:
263
- return False
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
- """kill child process
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
- pgid = os.getpgid(self.childPID)
272
- if pgid != os.getpgrp():
273
- try:
274
- # Child is in its own group: kill the group
275
- os.killpg(pgid, signal.SIGTERM)
276
- except OSError:
277
- # Process is already dead
278
- pass
279
- else:
280
- # No separate group: walk the tree
281
- parent = psutil.Process(self.childPID)
282
- procs = parent.children(recursive=recursive)
283
- procs.append(parent)
284
- for p in procs:
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
- pass
289
- _gone, alive = psutil.wait_procs(procs, timeout=10)
290
- # Escalate any survivors
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
- self.childKilled = True
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.__killPid(pid)
323
-
324
- # HACK to avoid python bug
325
- # self.wait()
326
- retries = 10000
327
- while os.waitpid(pid, 0) == -1 and retries > 0:
328
- time.sleep(0.001)
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 " "function return value" % self.bufferLimit
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(self.child.pid)
476
+ exitStatus = self.__poll(child_process)
468
477
 
469
- while (0, 0) == exitStatus or exitStatus is None:
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(self.child.pid)
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
- """ :mod: SubprocessTests
7
- =======================
6
+ """:mod: SubprocessTests
7
+ =======================
8
8
 
9
- .. module: SubprocessTests
10
- :synopsis: unittest for Subprocess module
11
- .. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com
9
+ .. module: SubprocessTests
10
+ :synopsis: unittest for Subprocess module
11
+ .. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com
12
12
 
13
- unittest for Subprocess module
13
+ unittest for Subprocess module
14
14
  """
15
- import time
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 systemCall, shellCall, pythonCall, getChildrenPIDs, Subprocess
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
- """ Starts a DIRAC command inside an apptainer container.
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
- dirac_env_var = os.environ.get("DIRAC", os.getcwd())
56
- diracos_env_var = os.environ.get("DIRACOS", os.getcwd())
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", f"{os.getcwd()}"]) # bind current directory for dirac_container.sh
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.warning(f"Bind path {bind_path} does not exist, skipping")
93
- cmd.extend(["--cwd", f"{os.getcwd()}"]) # set working directory
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"{os.getcwd()}/dirac_container.sh"])
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
- self.dataOpSender.sendData(
723
- ftsJob.accountingDict,
724
- commitFlag=True,
725
- delayedCommit=True,
726
- startTime=fromString(ftsJob.submitTime),
727
- endTime=fromString(ftsJob.lastUpdate),
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
- from datetime import datetime, timedelta
13
+ import errno
14
14
  import fnmatch
15
15
  import os
16
16
  import time
17
- import errno
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=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()