DIRAC 9.0.0a54__py3-none-any.whl → 9.0.7__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 (166) hide show
  1. DIRAC/AccountingSystem/Client/AccountingCLI.py +0 -140
  2. DIRAC/AccountingSystem/Client/DataStoreClient.py +0 -13
  3. DIRAC/AccountingSystem/Client/Types/BaseAccountingType.py +0 -7
  4. DIRAC/AccountingSystem/ConfigTemplate.cfg +0 -5
  5. DIRAC/AccountingSystem/Service/DataStoreHandler.py +0 -72
  6. DIRAC/ConfigurationSystem/Client/Helpers/CSGlobals.py +0 -9
  7. DIRAC/ConfigurationSystem/Client/Helpers/Registry.py +34 -32
  8. DIRAC/ConfigurationSystem/Client/Helpers/Resources.py +11 -43
  9. DIRAC/ConfigurationSystem/Client/Helpers/test/Test_Helpers.py +0 -16
  10. DIRAC/ConfigurationSystem/Client/LocalConfiguration.py +14 -8
  11. DIRAC/ConfigurationSystem/Client/PathFinder.py +47 -8
  12. DIRAC/ConfigurationSystem/Client/SyncPlugins/CERNLDAPSyncPlugin.py +4 -1
  13. DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +9 -2
  14. DIRAC/ConfigurationSystem/Client/test/Test_PathFinder.py +41 -1
  15. DIRAC/ConfigurationSystem/private/RefresherBase.py +4 -2
  16. DIRAC/Core/DISET/ServiceReactor.py +11 -3
  17. DIRAC/Core/DISET/private/BaseClient.py +1 -2
  18. DIRAC/Core/DISET/private/Transports/M2SSLTransport.py +9 -7
  19. DIRAC/Core/Security/DiracX.py +12 -7
  20. DIRAC/Core/Security/IAMService.py +4 -3
  21. DIRAC/Core/Security/ProxyInfo.py +9 -5
  22. DIRAC/Core/Security/test/test_diracx_token_from_pem.py +161 -0
  23. DIRAC/Core/Tornado/Client/ClientSelector.py +4 -1
  24. DIRAC/Core/Tornado/Server/TornadoService.py +1 -1
  25. DIRAC/Core/Utilities/ClassAd/ClassAdLight.py +4 -290
  26. DIRAC/Core/Utilities/DErrno.py +5 -309
  27. DIRAC/Core/Utilities/Extensions.py +10 -1
  28. DIRAC/Core/Utilities/Graphs/GraphData.py +1 -1
  29. DIRAC/Core/Utilities/JDL.py +1 -195
  30. DIRAC/Core/Utilities/List.py +1 -124
  31. DIRAC/Core/Utilities/MySQL.py +101 -97
  32. DIRAC/Core/Utilities/Os.py +32 -1
  33. DIRAC/Core/Utilities/Platform.py +2 -107
  34. DIRAC/Core/Utilities/ReturnValues.py +7 -252
  35. DIRAC/Core/Utilities/StateMachine.py +12 -178
  36. DIRAC/Core/Utilities/TimeUtilities.py +10 -253
  37. DIRAC/Core/Utilities/test/Test_JDL.py +0 -3
  38. DIRAC/Core/Utilities/test/Test_Profiler.py +20 -20
  39. DIRAC/Core/scripts/dirac_agent.py +1 -1
  40. DIRAC/Core/scripts/dirac_apptainer_exec.py +16 -7
  41. DIRAC/Core/scripts/dirac_platform.py +1 -92
  42. DIRAC/DataManagementSystem/Agent/FTS3Agent.py +8 -7
  43. DIRAC/DataManagementSystem/Agent/RequestOperations/RemoveFile.py +7 -6
  44. DIRAC/DataManagementSystem/Client/FTS3Job.py +71 -34
  45. DIRAC/DataManagementSystem/DB/FTS3DB.py +3 -0
  46. DIRAC/DataManagementSystem/DB/FileCatalogComponents/DatasetManager/DatasetManager.py +1 -1
  47. DIRAC/DataManagementSystem/Utilities/DMSHelpers.py +6 -2
  48. DIRAC/DataManagementSystem/scripts/dirac_dms_create_moving_request.py +2 -0
  49. DIRAC/DataManagementSystem/scripts/dirac_dms_protocol_matrix.py +0 -1
  50. DIRAC/FrameworkSystem/Client/ComponentInstaller.py +4 -2
  51. DIRAC/FrameworkSystem/DB/ProxyDB.py +9 -5
  52. DIRAC/FrameworkSystem/Utilities/TokenManagementUtilities.py +3 -2
  53. DIRAC/FrameworkSystem/Utilities/diracx.py +2 -74
  54. DIRAC/FrameworkSystem/private/authorization/AuthServer.py +2 -2
  55. DIRAC/FrameworkSystem/scripts/dirac_login.py +2 -2
  56. DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +1 -1
  57. DIRAC/Interfaces/API/Dirac.py +27 -13
  58. DIRAC/Interfaces/API/DiracAdmin.py +42 -7
  59. DIRAC/Interfaces/API/Job.py +1 -0
  60. DIRAC/Interfaces/scripts/dirac_admin_allow_site.py +7 -1
  61. DIRAC/Interfaces/scripts/dirac_admin_ban_site.py +7 -1
  62. DIRAC/Interfaces/scripts/dirac_wms_job_parameters.py +0 -1
  63. DIRAC/MonitoringSystem/Client/Types/WMSHistory.py +4 -0
  64. DIRAC/MonitoringSystem/Client/WebAppClient.py +26 -0
  65. DIRAC/MonitoringSystem/ConfigTemplate.cfg +9 -0
  66. DIRAC/MonitoringSystem/DB/MonitoringDB.py +6 -25
  67. DIRAC/MonitoringSystem/Service/MonitoringHandler.py +0 -33
  68. DIRAC/MonitoringSystem/Service/WebAppHandler.py +599 -0
  69. DIRAC/MonitoringSystem/private/MainReporter.py +0 -3
  70. DIRAC/ProductionSystem/scripts/dirac_prod_get_trans.py +2 -3
  71. DIRAC/RequestManagementSystem/Agent/RequestExecutingAgent.py +8 -6
  72. DIRAC/RequestManagementSystem/ConfigTemplate.cfg +6 -6
  73. DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
  74. DIRAC/ResourceStatusSystem/Client/SiteStatus.py +4 -2
  75. DIRAC/ResourceStatusSystem/Command/FreeDiskSpaceCommand.py +3 -1
  76. DIRAC/ResourceStatusSystem/Utilities/CSHelpers.py +2 -31
  77. DIRAC/ResourceStatusSystem/scripts/dirac_rss_set_status.py +18 -4
  78. DIRAC/Resources/Catalog/RucioFileCatalogClient.py +1 -1
  79. DIRAC/Resources/Computing/AREXComputingElement.py +19 -3
  80. DIRAC/Resources/Computing/BatchSystems/Condor.py +126 -108
  81. DIRAC/Resources/Computing/BatchSystems/SLURM.py +5 -1
  82. DIRAC/Resources/Computing/BatchSystems/test/Test_SLURM.py +46 -0
  83. DIRAC/Resources/Computing/HTCondorCEComputingElement.py +37 -43
  84. DIRAC/Resources/Computing/SingularityComputingElement.py +6 -1
  85. DIRAC/Resources/Computing/test/Test_HTCondorCEComputingElement.py +67 -49
  86. DIRAC/Resources/Computing/test/Test_PoolComputingElement.py +2 -1
  87. DIRAC/Resources/IdProvider/CheckInIdProvider.py +13 -0
  88. DIRAC/Resources/IdProvider/IdProviderFactory.py +11 -3
  89. DIRAC/Resources/Storage/StorageBase.py +4 -2
  90. DIRAC/Resources/Storage/StorageElement.py +4 -4
  91. DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +10 -16
  92. DIRAC/TransformationSystem/Agent/TransformationAgent.py +22 -1
  93. DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +15 -15
  94. DIRAC/TransformationSystem/Client/Transformation.py +2 -1
  95. DIRAC/TransformationSystem/Client/TransformationClient.py +0 -7
  96. DIRAC/TransformationSystem/Client/Utilities.py +9 -0
  97. DIRAC/TransformationSystem/Service/TransformationManagerHandler.py +0 -336
  98. DIRAC/TransformationSystem/Utilities/ReplicationCLIParameters.py +3 -3
  99. DIRAC/TransformationSystem/scripts/dirac_production_runjoblocal.py +2 -4
  100. DIRAC/TransformationSystem/test/Test_replicationTransformation.py +5 -6
  101. DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
  102. DIRAC/WorkloadManagementSystem/Agent/JobAgent.py +1 -5
  103. DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +11 -7
  104. DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py +4 -3
  105. DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py +13 -13
  106. DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +10 -13
  107. DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +18 -51
  108. DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py +41 -1
  109. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +2 -0
  110. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobCleaningAgent.py +7 -9
  111. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PushJobAgent.py +1 -0
  112. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_SiteDirector.py +8 -2
  113. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +4 -5
  114. DIRAC/WorkloadManagementSystem/Client/DownloadInputData.py +7 -5
  115. DIRAC/WorkloadManagementSystem/Client/JobMonitoringClient.py +10 -11
  116. DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
  117. DIRAC/WorkloadManagementSystem/Client/JobStateUpdateClient.py +3 -0
  118. DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -152
  119. DIRAC/WorkloadManagementSystem/Client/SandboxStoreClient.py +25 -38
  120. DIRAC/WorkloadManagementSystem/Client/WMSClient.py +2 -3
  121. DIRAC/WorkloadManagementSystem/Client/test/Test_Client_DownloadInputData.py +29 -0
  122. DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg +4 -8
  123. DIRAC/WorkloadManagementSystem/DB/JobDB.py +40 -69
  124. DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
  125. DIRAC/WorkloadManagementSystem/DB/JobParametersDB.py +9 -9
  126. DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.py +3 -2
  127. DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +28 -39
  128. DIRAC/WorkloadManagementSystem/DB/StatusUtils.py +125 -0
  129. DIRAC/WorkloadManagementSystem/DB/tests/Test_JobDB.py +1 -1
  130. DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py +28 -0
  131. DIRAC/WorkloadManagementSystem/Executor/JobSanity.py +3 -3
  132. DIRAC/WorkloadManagementSystem/FutureClient/JobStateUpdateClient.py +2 -14
  133. DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +14 -9
  134. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +36 -10
  135. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py +4 -0
  136. DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +33 -154
  137. DIRAC/WorkloadManagementSystem/Service/JobMonitoringHandler.py +5 -323
  138. DIRAC/WorkloadManagementSystem/Service/JobStateUpdateHandler.py +0 -16
  139. DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py +6 -102
  140. DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +5 -51
  141. DIRAC/WorkloadManagementSystem/Service/WMSAdministratorHandler.py +16 -79
  142. DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -199
  143. DIRAC/WorkloadManagementSystem/Utilities/JobParameters.py +65 -3
  144. DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py +2 -64
  145. DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py +7 -171
  146. DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py +73 -7
  147. DIRAC/WorkloadManagementSystem/Utilities/PilotWrapper.py +2 -0
  148. DIRAC/WorkloadManagementSystem/Utilities/RemoteRunner.py +16 -0
  149. DIRAC/WorkloadManagementSystem/Utilities/Utils.py +36 -1
  150. DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py +15 -0
  151. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py +1 -5
  152. DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
  153. DIRAC/WorkloadManagementSystem/Utilities/test/Test_PilotWrapper.py +16 -0
  154. DIRAC/__init__.py +55 -54
  155. {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/METADATA +6 -4
  156. {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/RECORD +160 -160
  157. {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/WHEEL +1 -1
  158. {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/entry_points.txt +0 -3
  159. DIRAC/Core/Utilities/test/Test_List.py +0 -150
  160. DIRAC/Core/Utilities/test/Test_Time.py +0 -88
  161. DIRAC/TransformationSystem/scripts/dirac_transformation_archive.py +0 -30
  162. DIRAC/TransformationSystem/scripts/dirac_transformation_clean.py +0 -30
  163. DIRAC/TransformationSystem/scripts/dirac_transformation_remove_output.py +0 -30
  164. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobManager.py +0 -58
  165. {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/licenses/LICENSE +0 -0
  166. {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/top_level.txt +0 -0
@@ -50,6 +50,7 @@ When using a local condor_schedd look at the HTCondor documentation for enabling
50
50
 
51
51
  import datetime
52
52
  import errno
53
+ import json
53
54
  import os
54
55
  import subprocess
55
56
  import tempfile
@@ -63,10 +64,14 @@ from DIRAC.Core.Utilities.File import mkDir
63
64
  from DIRAC.Core.Utilities.List import breakListIntoChunks
64
65
  from DIRAC.Core.Utilities.Subprocess import systemCall
65
66
  from DIRAC.FrameworkSystem.private.authorization.utils.Tokens import writeToTokenFile
66
- from DIRAC.Resources.Computing.BatchSystems.Condor import HOLD_REASON_SUBCODE, parseCondorStatus, subTemplate
67
+ from DIRAC.Resources.Computing.BatchSystems.Condor import (
68
+ HOLD_REASON_SUBCODE,
69
+ STATE_ATTRIBUTES,
70
+ getCondorStatus,
71
+ subTemplate,
72
+ )
67
73
  from DIRAC.Resources.Computing.ComputingElement import ComputingElement
68
74
  from DIRAC.WorkloadManagementSystem.Client import PilotStatus
69
- from DIRAC.WorkloadManagementSystem.Client.PilotManagerClient import PilotManagerClient
70
75
 
71
76
  MANDATORY_PARAMETERS = ["Queue"]
72
77
  DEFAULT_WORKINGDIRECTORY = "/opt/dirac/pro/runit/WorkloadManagement/SiteDirectorHT"
@@ -386,33 +391,10 @@ class HTCondorCEComputingElement(ComputingElement):
386
391
 
387
392
  #############################################################################
388
393
  def getCEStatus(self):
389
- """Method to return information on running and pending jobs.
390
-
391
- Warning: information currently returned depends on the PilotManager and not HTCondor.
392
- Results might be wrong if pilots or jobs are submitted manually via the CE.
393
- """
394
- result = S_OK()
395
- result["SubmittedJobs"] = 0
396
- result["RunningJobs"] = 0
397
- result["WaitingJobs"] = 0
398
-
399
- # getWaitingPilots
400
- condDict = {"DestinationSite": self.ceName, "Status": PilotStatus.PILOT_WAITING_STATES}
401
- res = PilotManagerClient().countPilots(condDict)
402
- if res["OK"]:
403
- result["WaitingJobs"] = int(res["Value"])
404
- else:
405
- self.log.warn(f"Failure getting pilot count for {self.ceName}: {res['Message']} ")
406
-
407
- # getRunningPilots
408
- condDict = {"DestinationSite": self.ceName, "Status": PilotStatus.RUNNING}
409
- res = PilotManagerClient().countPilots(condDict)
410
- if res["OK"]:
411
- result["RunningJobs"] = int(res["Value"])
412
- else:
413
- self.log.warn(f"Failure getting pilot count for {self.ceName}: {res['Message']} ")
414
-
415
- return result
394
+ """Method to return information on running and pending jobs."""
395
+ return S_ERROR(
396
+ "getCEStatus() not supported for HTCondorCEComputingElement: HTCondor does not expose this information"
397
+ )
416
398
 
417
399
  def getJobStatus(self, jobIDList):
418
400
  """Get the status information for the given list of jobs"""
@@ -424,45 +406,57 @@ class HTCondorCEComputingElement(ComputingElement):
424
406
  if isinstance(jobIDList, str):
425
407
  jobIDList = [jobIDList]
426
408
 
409
+ self.tokenFile = None
427
410
  resultDict = {}
428
411
  condorIDs = {}
429
412
  # Get all condorIDs so we can just call condor_q and condor_history once
430
413
  for jobReference in jobIDList:
431
414
  jobReference = jobReference.split(":::")[0]
432
- condorIDs[jobReference] = self._jobReferenceToCondorID(jobReference)
415
+ condorIDs[self._jobReferenceToCondorID(jobReference)] = jobReference
433
416
 
434
- self.tokenFile = None
435
-
436
- qList = []
437
- for _condorIDs in breakListIntoChunks(condorIDs.values(), 100):
438
- # This will return a list of 1245.75 3 undefined undefined undefined
417
+ jobsMetadata = []
418
+ for _condorIDs in breakListIntoChunks(condorIDs.keys(), 100):
439
419
  cmd = ["condor_q"]
440
420
  cmd.extend(self.remoteScheddOptions.strip().split(" "))
441
421
  cmd.extend(_condorIDs)
442
- cmd.extend(["-af:j", "JobStatus", "HoldReasonCode", "HoldReasonSubCode", "HoldReason"])
422
+ cmd.extend(["-attributes", STATE_ATTRIBUTES])
423
+ cmd.extend(["-json"])
443
424
  result = self._executeCondorCommand(cmd, keepTokenFile=True)
444
425
  if not result["OK"]:
445
426
  return result
446
427
 
447
- qList.extend(result["Value"].split("\n"))
428
+ if result["Value"]:
429
+ jobsMetadata.extend(json.loads(result["Value"]))
448
430
 
449
431
  condorHistCall = ["condor_history"]
450
432
  condorHistCall.extend(self.remoteScheddOptions.strip().split(" "))
451
433
  condorHistCall.extend(_condorIDs)
452
- condorHistCall.extend(["-af:j", "JobStatus", "HoldReasonCode", "HoldReasonSubCode", "HoldReason"])
434
+ condorHistCall.extend(["-attributes", STATE_ATTRIBUTES])
435
+ condorHistCall.extend(["-json"])
453
436
  result = self._executeCondorCommand(cmd, keepTokenFile=True)
454
437
  if not result["OK"]:
455
438
  return result
456
439
 
457
- qList.extend(result["Value"].split("\n"))
440
+ if result["Value"]:
441
+ jobsMetadata.extend(json.loads(result["Value"]))
458
442
 
459
- for job, jobID in condorIDs.items():
460
- jobStatus, reason = parseCondorStatus(qList, jobID)
443
+ foundJobIDs = set()
444
+ for jobDict in jobsMetadata:
445
+ jobStatus, reason = getCondorStatus(jobDict)
446
+ condorId = f"{jobDict['ClusterId']}.{jobDict['ProcId']}"
447
+ jobReference = condorIDs.get(condorId)
461
448
 
462
449
  if jobStatus == PilotStatus.ABORTED:
463
- self.log.verbose("Job", f"{jobID} held: {reason}")
450
+ self.log.verbose("Job", f"{jobReference} held: {reason}")
451
+
452
+ resultDict[jobReference] = jobStatus
453
+ foundJobIDs.add(jobReference)
464
454
 
465
- resultDict[job] = jobStatus
455
+ # Check if we have any jobs that were not found in the condor_q or condor_history
456
+ for jobReference in condorIDs.values():
457
+ if jobReference not in foundJobIDs:
458
+ self.log.verbose("Job", f"{jobReference} not found in condor_q or condor_history")
459
+ resultDict[jobReference] = PilotStatus.UNKNOWN
466
460
 
467
461
  self.tokenFile = None
468
462
 
@@ -387,7 +387,12 @@ class SingularityComputingElement(ComputingElement):
387
387
  continue
388
388
 
389
389
  mountedPath = se.getStorageParameters(protocol="file")["Value"]["Path"]
390
- bindPaths.append(f"{mountedPath}:{mountedPath}:ro")
390
+ mountOptions = se.getStorageParameters(protocol="file")["Value"].get("MountOptions", "ro")
391
+ if mountOptions not in ("rw", "ro"):
392
+ self.log.warn(f"Unknown mount mode: {mountOptions}")
393
+ continue
394
+
395
+ bindPaths.append(f"{mountedPath}:{mountedPath}:{mountOptions}")
391
396
  except KeyError:
392
397
  pass
393
398
 
@@ -2,6 +2,7 @@
2
2
  """
3
3
  tests for HTCondorCEComputingElement module
4
4
  """
5
+ import json
5
6
  import uuid
6
7
 
7
8
  import pytest
@@ -12,18 +13,30 @@ from DIRAC.Resources.Computing.BatchSystems import Condor
12
13
 
13
14
  MODNAME = "DIRAC.Resources.Computing.HTCondorCEComputingElement"
14
15
 
15
- STATUS_LINES = """
16
- 123.2 5 4 0 undefined
17
- 123.1 3 undefined undefined undefined
18
- """.strip().split(
19
- "\n"
20
- )
21
-
22
- HISTORY_LINES = """
23
- 123.0 4 undefined undefined undefined
24
- """.strip().split(
25
- "\n"
26
- )
16
+ STATUS_QUEUE = [
17
+ {
18
+ "ClusterId": 123,
19
+ "ProcId": 2,
20
+ "JobStatus": 5,
21
+ "HoldReasonCode": 4,
22
+ "HoldReasonSubCode": 0,
23
+ "HoldReason": "The credentials for the job are invalid",
24
+ },
25
+ {
26
+ "ClusterId": 123,
27
+ "ProcId": 1,
28
+ "JobStatus": 3,
29
+ },
30
+ ]
31
+
32
+
33
+ STATUS_HISTORY = [
34
+ {
35
+ "ClusterId": 123,
36
+ "ProcId": 0,
37
+ "JobStatus": 4,
38
+ }
39
+ ]
27
40
 
28
41
 
29
42
  @pytest.fixture
@@ -32,42 +45,47 @@ def setUp():
32
45
 
33
46
 
34
47
  def test_parseCondorStatus():
35
- statusLines = f"""
36
- 104098.1 1 undefined undefined undefined
37
- 104098.2 2 undefined undefined undefined
38
- 104098.3 3 undefined undefined undefined
39
- 104098.4 4 undefined undefined undefined
40
- 104098.5 5 16 57 Input data are being spooled
41
- 104098.6 5 3 {Condor.HOLD_REASON_SUBCODE} Policy
42
- 104098.7 5 1 0 undefined
43
-
44
- foo bar
45
- 104096.1 3 16 test test
46
- 104096.2 3 test
47
- 104096.3 5 undefined undefined undefined
48
- 104096.4 7
49
- """.strip().split(
50
- "\n"
51
- )
52
- # force there to be an empty line
48
+ statusOutput = {"ClusterId": 104098, "ProcId": 1, "JobStatus": 1}
49
+ assert HTCE.getCondorStatus(statusOutput) == ("Waiting", "")
53
50
 
54
- expectedResults = {
55
- "104098.1": "Waiting",
56
- "104098.2": "Running",
57
- "104098.3": "Aborted",
58
- "104098.4": "Done",
59
- "104098.5": "Waiting",
60
- "104098.6": "Failed",
61
- "104098.7": "Aborted",
62
- "foo": "Unknown",
63
- "104096.1": "Aborted",
64
- "104096.2": "Aborted",
65
- "104096.3": "Aborted",
66
- "104096.4": "Unknown",
67
- }
51
+ statusOutput = {"ClusterId": 104098, "ProcId": 2, "JobStatus": 2}
52
+ assert HTCE.getCondorStatus(statusOutput) == ("Running", "")
53
+
54
+ statusOutput = {"ClusterId": 104098, "ProcId": 3, "JobStatus": 3}
55
+ assert HTCE.getCondorStatus(statusOutput) == ("Aborted", "")
56
+
57
+ statusOutput = {"ClusterId": 104098, "ProcId": 4, "JobStatus": 4}
58
+ assert HTCE.getCondorStatus(statusOutput) == ("Done", "")
68
59
 
69
- for jobID, expected in expectedResults.items():
70
- assert HTCE.parseCondorStatus(statusLines, jobID)[0] == expected
60
+ statusOutput = {
61
+ "ClusterId": 104098,
62
+ "ProcId": 5,
63
+ "JobStatus": 5,
64
+ "HoldReasonCode": 16,
65
+ "HoldReasonSubCode": 57,
66
+ "HoldReason": "Input data are being spooled",
67
+ }
68
+ assert HTCE.getCondorStatus(statusOutput) == ("Waiting", "Input data are being spooled")
69
+
70
+ statusOutput = {
71
+ "ClusterId": 104098,
72
+ "ProcId": 6,
73
+ "JobStatus": 5,
74
+ "HoldReasonCode": 3,
75
+ "HoldReasonSubCode": HTCE.HOLD_REASON_SUBCODE,
76
+ "HoldReason": "Policy",
77
+ }
78
+ assert HTCE.getCondorStatus(statusOutput) == ("Failed", "Policy")
79
+
80
+ statusOutput = {
81
+ "ClusterId": 104098,
82
+ "ProcId": 7,
83
+ "JobStatus": 5,
84
+ "HoldReasonCode": 1,
85
+ "HoldReasonSubCode": 0,
86
+ "HoldReason": "Aborted by user",
87
+ }
88
+ assert HTCE.getCondorStatus(statusOutput) == ("Aborted", "Aborted by user")
71
89
 
72
90
 
73
91
  def test_getJobStatus(mocker):
@@ -75,8 +93,8 @@ def test_getJobStatus(mocker):
75
93
  mocker.patch(
76
94
  MODNAME + ".systemCall",
77
95
  side_effect=[
78
- S_OK((0, "\n".join(STATUS_LINES), "")),
79
- S_OK((0, "\n".join(HISTORY_LINES), "")),
96
+ S_OK((0, json.dumps(STATUS_QUEUE), "")),
97
+ S_OK((0, json.dumps(STATUS_HISTORY), "")),
80
98
  S_OK((0, "", "")),
81
99
  S_OK((0, "", "")),
82
100
  ],
@@ -110,7 +128,7 @@ def test_getJobStatus(mocker):
110
128
  def test_getJobStatusBatchSystem(mocker):
111
129
  """Test Condor Batch System plugin getJobStatus"""
112
130
  patchPopen = mocker.patch("DIRAC.Resources.Computing.BatchSystems.Condor.subprocess.Popen")
113
- patchPopen.return_value.communicate.side_effect = [("\n".join(STATUS_LINES), ""), ("\n".join(HISTORY_LINES), "")]
131
+ patchPopen.return_value.communicate.side_effect = [(json.dumps(STATUS_QUEUE), ""), (json.dumps(STATUS_HISTORY), "")]
114
132
  patchPopen.return_value.returncode = 0
115
133
 
116
134
  ret = Condor.Condor().getJobStatus(JobIDList=["123.0", "123.1", "123.2", "333.3"])
@@ -25,7 +25,7 @@ while True:
25
25
  if os.path.isfile(stopFile):
26
26
  os.remove(stopFile)
27
27
  break
28
- if (time.time() - start) > 30:
28
+ if (time.time() - start) > 5:
29
29
  break
30
30
  print("End job", jobNumber, time.time())
31
31
  """
@@ -262,6 +262,7 @@ def test_executeJob_wholeNode8(createAndDelete):
262
262
  assert "Not enough processors" in submissionResult["Message"]
263
263
 
264
264
 
265
+ @pytest.mark.slow
265
266
  def test_executeJob_submitAndStop(createAndDelete):
266
267
  time.sleep(0.5)
267
268
 
@@ -26,3 +26,16 @@ class CheckInIdProvider(OAuth2IdProvider):
26
26
 
27
27
  idPScope = f"eduperson_entitlement?value=urn:mace:egi.eu:group:{vo}:role={groupElements[1]}#aai.egi.eu"
28
28
  return scope_to_list(idPScope)
29
+
30
+ def fetchToken(self, **kwargs):
31
+ """Fetch token
32
+
33
+ :param kwargs:
34
+ :return: dict
35
+ """
36
+
37
+ if "audience" in kwargs:
38
+ kwargs["resource"] = kwargs["audience"]
39
+ kwargs.pop("audience")
40
+
41
+ return super().fetchToken(**kwargs)
@@ -1,6 +1,7 @@
1
- """ The Identity Provider Factory instantiates IdProvider objects
2
- according to their configuration
1
+ """The Identity Provider Factory instantiates IdProvider objects
2
+ according to their configuration
3
3
  """
4
+
4
5
  import jwt
5
6
 
6
7
  from DIRAC import S_OK, S_ERROR, gLogger, gConfig
@@ -40,11 +41,12 @@ class IdProviderFactory:
40
41
  return result
41
42
  return self.getIdProvider(result["Value"])
42
43
 
43
- def getIdProvider(self, name, **kwargs):
44
+ def getIdProvider(self, name, client_name_prefix="", **kwargs):
44
45
  """This method returns a IdProvider instance corresponding to the supplied
45
46
  name.
46
47
 
47
48
  :param str name: the name of the Identity Provider client
49
+ :param str client_name_prefix: name of the client of the IdP
48
50
 
49
51
  :return: S_OK(IdProvider)/S_ERROR()
50
52
  """
@@ -68,8 +70,14 @@ class IdProviderFactory:
68
70
  if not result["OK"]:
69
71
  self.log.error("Failed to read configuration", f"{name}: {result['Message']}")
70
72
  return result
73
+
71
74
  pDict = result["Value"]
72
75
 
76
+ if client_name_prefix:
77
+ client_name_prefix = client_name_prefix + "_"
78
+ pDict["client_id"] = pDict[f"{client_name_prefix}client_id"]
79
+ pDict["client_secret"] = pDict[f"{client_name_prefix}client_secret"]
80
+
73
81
  pDict.update(kwargs)
74
82
  pDict["ProviderName"] = name
75
83
 
@@ -1,4 +1,4 @@
1
- """ Base Storage Class provides the base interface for all storage plug-ins
1
+ """Base Storage Class provides the base interface for all storage plug-ins
2
2
 
3
3
  exists()
4
4
 
@@ -33,6 +33,7 @@ These are the methods for getting information about the Storage:
33
33
  getOccupancy()
34
34
 
35
35
  """
36
+
36
37
  import errno
37
38
  import json
38
39
  import os
@@ -113,7 +114,8 @@ class StorageBase:
113
114
 
114
115
  def getParameters(self):
115
116
  """Get the parameters with which the storage was instantiated"""
116
- parameterDict = dict(self.protocolParameters)
117
+ parameterDict = dict(self._allProtocolParameters)
118
+ parameterDict.update(self.protocolParameters)
117
119
  parameterDict["StorageName"] = self.name
118
120
  parameterDict["PluginName"] = self.pluginName
119
121
  parameterDict["URLBase"] = self.getURLBase().get("Value", "")
@@ -438,10 +438,6 @@ class StorageElementItem:
438
438
 
439
439
  kwargs["occupancyLFN"] = occupancyLFN
440
440
 
441
- filteredPlugins = self.__filterPlugins("getOccupancy")
442
- if not filteredPlugins:
443
- return S_ERROR(errno.EPROTONOSUPPORT, "No storage plugins to query the occupancy")
444
-
445
441
  # Call occupancy plugin if requested
446
442
  occupancyPlugin = self.options.get("OccupancyPlugin")
447
443
  if occupancyPlugin:
@@ -459,6 +455,10 @@ class StorageElementItem:
459
455
  except Exception as e:
460
456
  return S_ERROR(f"Occupancy plugin failed: {str(e)}")
461
457
 
458
+ filteredPlugins = self.__filterPlugins("getOccupancy")
459
+ if not filteredPlugins:
460
+ return S_ERROR(errno.EPROTONOSUPPORT, "No storage plugins to query the occupancy")
461
+
462
462
  # Try all of the storages one by one
463
463
  for storage in filteredPlugins:
464
464
  # The result of the plugin is always in B
@@ -7,23 +7,21 @@
7
7
  In case you want to further extend it you are required to follow the note on the
8
8
  initialize method and on the _getClients method.
9
9
  """
10
- import time
11
- import datetime
12
10
  import concurrent.futures
11
+ import datetime
12
+ import time
13
13
 
14
- from DIRAC import S_OK
15
-
14
+ from DIRAC import S_OK, gConfig
15
+ from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
16
16
  from DIRAC.Core.Base.AgentModule import AgentModule
17
17
  from DIRAC.Core.Security.ProxyInfo import getProxyInfo
18
- from DIRAC.Core.Utilities.List import breakListIntoChunks
19
18
  from DIRAC.Core.Utilities.Dictionaries import breakDictionaryIntoChunks
20
- from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
19
+ from DIRAC.Core.Utilities.List import breakListIntoChunks
20
+ from DIRAC.TransformationSystem.Agent.TransformationAgentsUtilities import TransformationAgentsUtilities
21
21
  from DIRAC.TransformationSystem.Client.FileReport import FileReport
22
- from DIRAC.TransformationSystem.Client.WorkflowTasks import WorkflowTasks
23
22
  from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient
24
- from DIRAC.TransformationSystem.Agent.TransformationAgentsUtilities import TransformationAgentsUtilities
23
+ from DIRAC.TransformationSystem.Client.WorkflowTasks import WorkflowTasks
25
24
  from DIRAC.WorkloadManagementSystem.Client import JobStatus
26
- from DIRAC.WorkloadManagementSystem.Client.JobManagerClient import JobManagerClient
27
25
 
28
26
  AGENT_NAME = "Transformation/TaskManagerAgentBase"
29
27
 
@@ -40,7 +38,6 @@ class TaskManagerAgentBase(AgentModule, TransformationAgentsUtilities):
40
38
  TransformationAgentsUtilities.__init__(self)
41
39
 
42
40
  self.transClient = None
43
- self.jobManagerClient = None
44
41
  self.transType = []
45
42
 
46
43
  self.tasksPerLoop = 50
@@ -69,7 +66,6 @@ class TaskManagerAgentBase(AgentModule, TransformationAgentsUtilities):
69
66
 
70
67
  # Default clients
71
68
  self.transClient = TransformationClient()
72
- self.jobManagerClient = JobManagerClient()
73
69
 
74
70
  # Bulk submission flag
75
71
  self.bulkSubmissionFlag = self.am_getOption("BulkSubmission", self.bulkSubmissionFlag)
@@ -193,11 +189,9 @@ class TaskManagerAgentBase(AgentModule, TransformationAgentsUtilities):
193
189
  else:
194
190
  # Get the transformations which should be submitted
195
191
  self.tasksPerLoop = self.am_getOption("TasksPerLoop", self.tasksPerLoop)
196
- res = self.jobManagerClient.getMaxParametricJobs()
197
- if not res["OK"]:
198
- self.log.warn("Could not get the maxParametricJobs from JobManager", res["Message"])
199
- else:
200
- self.maxParametricJobs = res["Value"]
192
+ self.maxParametricJobs = gConfig.getValue(
193
+ "/Systems/WorkloadManagement/Services/JobManager/MaxParametricJobs", self.maxParametricJobs
194
+ )
201
195
 
202
196
  self._addOperationForTransformations(
203
197
  self.operationsOnTransformationDict,
@@ -1,4 +1,4 @@
1
- """ TransformationAgent processes transformations found in the transformation database.
1
+ """TransformationAgent processes transformations found in the transformation database.
2
2
 
3
3
  The following options can be set for the TransformationAgent.
4
4
 
@@ -8,6 +8,7 @@ The following options can be set for the TransformationAgent.
8
8
  :dedent: 2
9
9
  :caption: TransformationAgent options
10
10
  """
11
+
11
12
  from importlib import import_module
12
13
 
13
14
  import time
@@ -15,6 +16,7 @@ import os
15
16
  import datetime
16
17
  import pickle
17
18
  import concurrent.futures
19
+ from pathlib import Path
18
20
 
19
21
  from DIRAC import S_OK, S_ERROR
20
22
  from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
@@ -127,6 +129,9 @@ class TransformationAgent(AgentModule, TransformationAgentsUtilities):
127
129
  if not res["OK"]:
128
130
  self._logError("Failed to obtain transformations:", res["Message"])
129
131
  return S_OK()
132
+
133
+ active_trans_ids = [t["TransformationID"] for t in res["Value"]]
134
+ self.cleanOldTransformationCache(active_trans_ids)
130
135
  # Process the transformations
131
136
  count = 0
132
137
  future_to_transID = {}
@@ -164,6 +169,22 @@ class TransformationAgent(AgentModule, TransformationAgentsUtilities):
164
169
 
165
170
  return S_OK()
166
171
 
172
+ def cleanOldTransformationCache(self, active_trans_ids: list[int]):
173
+ cache_filenames = {Path(self.__cacheFile(tid)) for tid in active_trans_ids}
174
+ existing_caches = set(Path(self.workDirectory).glob("*.pkl"))
175
+ useless_cache_files = existing_caches - cache_filenames
176
+
177
+ if useless_cache_files:
178
+ self._logInfo(f"Found potentially {len(useless_cache_files)} useless cache files")
179
+
180
+ # Since idle transformations aren't in active_trans_ids, let's filter it more
181
+ # and take only files that haven't been touched for 2 month
182
+ last_update_threshold = (datetime.datetime.utcnow() - datetime.timedelta(days=60)).timestamp()
183
+
184
+ for cache_file in useless_cache_files:
185
+ if Path(cache_file).stat().st_mtime < last_update_threshold:
186
+ cache_file.unlink()
187
+
167
188
  def getTransformations(self):
168
189
  """Obtain the transformations to be executed - this is executed at the start of every loop (it's really the
169
190
  only real thing in the execute()
@@ -16,14 +16,12 @@ from datetime import datetime, timedelta
16
16
 
17
17
  # # from DIRAC
18
18
  from DIRAC import S_ERROR, S_OK
19
- from DIRAC.ConfigurationSystem.Client.ConfigurationData import gConfigurationData
20
19
  from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
21
20
  from DIRAC.Core.Base.AgentModule import AgentModule
22
21
  from DIRAC.Core.Utilities.DErrno import cmpError
23
22
  from DIRAC.Core.Utilities.List import breakListIntoChunks
24
23
  from DIRAC.Core.Utilities.Proxy import executeWithUserProxy
25
24
  from DIRAC.Core.Utilities.ReturnValues import returnSingleResult
26
- from DIRAC.DataManagementSystem.Client.DataManager import DataManager
27
25
  from DIRAC.RequestManagementSystem.Client.File import File
28
26
  from DIRAC.RequestManagementSystem.Client.Operation import Operation
29
27
  from DIRAC.RequestManagementSystem.Client.ReqClient import ReqClient
@@ -34,8 +32,12 @@ from DIRAC.Resources.Catalog.FileCatalogClient import FileCatalogClient
34
32
  from DIRAC.Resources.Storage.StorageElement import StorageElement
35
33
  from DIRAC.TransformationSystem.Client import TransformationStatus
36
34
  from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient
37
- from DIRAC.WorkloadManagementSystem.Client.JobMonitoringClient import JobMonitoringClient
38
- from DIRAC.WorkloadManagementSystem.Client.WMSClient import WMSClient
35
+ from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
36
+ from DIRAC.WorkloadManagementSystem.Service.JobPolicy import (
37
+ RIGHT_DELETE,
38
+ RIGHT_KILL,
39
+ )
40
+ from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
39
41
 
40
42
  # # agent's name
41
43
  AGENT_NAME = "Transformation/TransformationCleaningAgent"
@@ -59,12 +61,12 @@ class TransformationCleaningAgent(AgentModule):
59
61
 
60
62
  # # transformation client
61
63
  self.transClient = None
62
- # # wms client
63
- self.wmsClient = None
64
64
  # # request client
65
65
  self.reqClient = None
66
66
  # # file catalog client
67
67
  self.metadataClient = None
68
+ # # JobDB
69
+ self.jobDB = None
68
70
 
69
71
  # # transformations types
70
72
  self.transformationTypes = None
@@ -119,14 +121,12 @@ class TransformationCleaningAgent(AgentModule):
119
121
 
120
122
  # # transformation client
121
123
  self.transClient = TransformationClient()
122
- # # wms client
123
- self.wmsClient = WMSClient()
124
124
  # # request client
125
125
  self.reqClient = ReqClient()
126
126
  # # file catalog client
127
127
  self.metadataClient = FileCatalogClient()
128
- # # job monitoring client
129
- self.jobMonitoringClient = JobMonitoringClient()
128
+ # # job DB
129
+ self.jobDB = JobDB()
130
130
 
131
131
  return S_OK()
132
132
 
@@ -224,7 +224,7 @@ class TransformationCleaningAgent(AgentModule):
224
224
  So, we should just clean from time to time.
225
225
  What I added here is done only when the agent finalize, and it's quite light-ish operation anyway.
226
226
  """
227
- res = self.jobMonitoringClient.getJobGroups(None, datetime.utcnow() - timedelta(days=365))
227
+ res = self.jobDB.getDistinctJobAttributes("JobGroup", None, datetime.utcnow() - timedelta(days=365))
228
228
  if not res["OK"]:
229
229
  self.log.error("Failed to get job groups", res["Message"])
230
230
  return res
@@ -268,7 +268,7 @@ class TransformationCleaningAgent(AgentModule):
268
268
 
269
269
  # Remove JobIDs that were unknown to the TransformationSystem
270
270
  jobGroupsToCheck = [str(transDict["TransformationID"]).zfill(8) for transDict in toClean + toArchive]
271
- res = self.jobMonitoringClient.getJobs({"JobGroup": jobGroupsToCheck})
271
+ res = self.jobDB.selectJobs({"JobGroup": jobGroupsToCheck})
272
272
  if not res["OK"]:
273
273
  return res
274
274
  jobIDsToRemove = [int(jobID) for jobID in res["Value"]]
@@ -610,8 +610,8 @@ class TransformationCleaningAgent(AgentModule):
610
610
  # Prevent 0 job IDs
611
611
  jobIDs = [int(j) for j in transJobIDs if int(j)]
612
612
  allRemove = True
613
- for jobList in breakListIntoChunks(jobIDs, 500):
614
- res = self.wmsClient.killJob(jobList, force=True)
613
+ for jobList in breakListIntoChunks(jobIDs, 1000):
614
+ res = kill_delete_jobs(RIGHT_KILL, jobList, force=True)
615
615
  if res["OK"]:
616
616
  self.log.info(f"Successfully killed {len(jobList)} jobs from WMS")
617
617
  elif ("InvalidJobIDs" in res) and ("NonauthorizedJobIDs" not in res) and ("FailedJobIDs" not in res):
@@ -623,7 +623,7 @@ class TransformationCleaningAgent(AgentModule):
623
623
  self.log.error("Failed to kill jobs", f"(n={len(res['FailedJobIDs'])})")
624
624
  allRemove = False
625
625
 
626
- res = self.wmsClient.deleteJob(jobList)
626
+ res = kill_delete_jobs(RIGHT_DELETE, jobList, force=True)
627
627
  if res["OK"]:
628
628
  self.log.info("Successfully deleted jobs from WMS", f"(n={len(jobList)})")
629
629
  elif ("InvalidJobIDs" in res) and ("NonauthorizedJobIDs" not in res) and ("FailedJobIDs" not in res):
@@ -9,6 +9,7 @@ from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
9
9
  from DIRAC.Core.Base.API import API
10
10
  from DIRAC.Core.Utilities.JEncode import encode
11
11
  from DIRAC.Core.Utilities.PromptUser import promptUser
12
+ from DIRAC.MonitoringSystem.Client.WebAppClient import WebAppClient
12
13
  from DIRAC.RequestManagementSystem.Client.Operation import Operation
13
14
  from DIRAC.TransformationSystem.Client.BodyPlugin.BaseBody import BaseBody
14
15
  from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient
@@ -498,7 +499,7 @@ class Transformation(API):
498
499
  ]
499
500
  dictList = []
500
501
 
501
- result = self.transClient.getTransformationSummaryWeb(condDict, orderby, start, maxitems)
502
+ result = WebAppClient().getTransformationSummaryWeb(condDict, orderby, start, maxitems)
502
503
  if not result["OK"]:
503
504
  self._prettyPrint(result)
504
505
  return result