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
@@ -11,8 +11,11 @@ The following options can be set in ``Systems/WorkloadManagement/Databases/JobDB
11
11
  * *CompressJDLs*: Enable compression of JDLs when they are stored in the database, default *False*.
12
12
 
13
13
  """
14
+ from __future__ import annotations
15
+
14
16
  import datetime
15
17
  import operator
18
+ from typing import overload
16
19
 
17
20
  from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getVOForGroup
18
21
  from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getSiteTier
@@ -20,7 +23,14 @@ from DIRAC.Core.Base.DB import DB
20
23
  from DIRAC.Core.Utilities.ClassAd.ClassAdLight import ClassAd
21
24
  from DIRAC.Core.Utilities.Decorators import deprecated
22
25
  from DIRAC.Core.Utilities.DErrno import EWMSJMAN, EWMSSUBM, cmpError
23
- from DIRAC.Core.Utilities.ReturnValues import S_ERROR, S_OK, convertToReturnValue, returnValueOrRaise, SErrorException
26
+ from DIRAC.Core.Utilities.ReturnValues import (
27
+ S_ERROR,
28
+ S_OK,
29
+ convertToReturnValue,
30
+ returnValueOrRaise,
31
+ SErrorException,
32
+ DReturnType,
33
+ )
24
34
  from DIRAC.FrameworkSystem.Client.Logger import contextLogger
25
35
  from DIRAC.ResourceStatusSystem.Client.SiteStatus import SiteStatus
26
36
  from DIRAC.WorkloadManagementSystem.Client import JobMinorStatus, JobStatus
@@ -320,23 +330,42 @@ class JobDB(DB):
320
330
 
321
331
  #############################################################################
322
332
 
323
- def getInputData(self, jobID):
333
+ @overload
334
+ def getInputData(self, jobID: int | str) -> DReturnType[list[str]]:
335
+ ...
336
+
337
+ @overload
338
+ def getInputData(self, jobID: list[int | str]) -> DReturnType[dict[int, list[str]]]:
339
+ ...
340
+
341
+ def getInputData(self, jobID: int | str | list[int | str]) -> DReturnType[list[str] | dict[int, list[str]]]:
324
342
  """Get input data for the given job"""
325
- ret = self._escapeString(jobID)
326
- if not ret["OK"]:
327
- return ret
328
- jobID = ret["Value"]
329
- cmd = f"SELECT LFN FROM InputData WHERE JobID={jobID}"
343
+ if isinstance(jobID, (int, str)):
344
+ ret = self._escapeString(jobID)
345
+ if not ret["OK"]:
346
+ return ret
347
+ jobID = ret["Value"]
348
+ query = f"JobID={jobID}"
349
+ result = []
350
+ else:
351
+ job_ids = {int(i) for i in jobID}
352
+ query = f"JobID IN ({','.join(map(str, job_ids))})"
353
+ result = {i: [] for i in job_ids}
354
+ cmd = f"SELECT JobID, LFN FROM InputData WHERE {query}"
330
355
  res = self._query(cmd)
331
356
  if not res["OK"]:
332
357
  return res
333
358
 
334
- inputData = [i[0] for i in res["Value"] if i[0].strip()]
335
- for index, lfn in enumerate(inputData):
359
+ for jid, lfn in res["Value"]:
360
+ lfn = lfn.strip()
336
361
  if lfn.lower().startswith("lfn:"):
337
- inputData[index] = lfn[4:]
362
+ lfn = lfn[4:]
363
+ if isinstance(result, list):
364
+ result.append(lfn)
365
+ else:
366
+ result[jid].append(lfn)
338
367
 
339
- return S_OK(inputData)
368
+ return S_OK(result)
340
369
 
341
370
  #############################################################################
342
371
  def setInputData(self, jobID, inputData):
@@ -1161,64 +1190,6 @@ class JobDB(DB):
1161
1190
 
1162
1191
  return retVal
1163
1192
 
1164
- #############################################################################
1165
- def getSiteSummary(self):
1166
- """Get the summary of jobs in a given status on all the sites"""
1167
-
1168
- waitingList = ['"Submitted"', '"Assigned"', '"Waiting"', '"Matched"']
1169
- waitingString = ",".join(waitingList)
1170
-
1171
- result = self.getDistinctJobAttributes("Site")
1172
- if not result["OK"]:
1173
- return result
1174
-
1175
- siteList = result["Value"]
1176
- siteDict = {}
1177
- totalDict = {
1178
- JobStatus.WAITING: 0,
1179
- JobStatus.RUNNING: 0,
1180
- JobStatus.STALLED: 0,
1181
- JobStatus.DONE: 0,
1182
- JobStatus.FAILED: 0,
1183
- }
1184
-
1185
- for site in siteList:
1186
- if site == "ANY":
1187
- continue
1188
- # Waiting
1189
- siteDict[site] = {}
1190
- ret = self._escapeString(site)
1191
- if not ret["OK"]:
1192
- return ret
1193
- e_site = ret["Value"]
1194
-
1195
- req = f"SELECT COUNT(JobID) FROM Jobs WHERE Status IN ({waitingString}) AND Site={e_site}"
1196
- result = self._query(req)
1197
- if result["OK"]:
1198
- count = result["Value"][0][0]
1199
- else:
1200
- return S_ERROR("Failed to get Site data from the JobDB")
1201
- siteDict[site][JobStatus.WAITING] = count
1202
- totalDict[JobStatus.WAITING] += count
1203
- # Running,Stalled,Done,Failed
1204
- for status in [
1205
- f'"{JobStatus.RUNNING}"',
1206
- f'"{JobStatus.STALLED}"',
1207
- f'"{JobStatus.DONE}"',
1208
- f'"{JobStatus.FAILED}"',
1209
- ]:
1210
- req = f"SELECT COUNT(JobID) FROM Jobs WHERE Status={status} AND Site={e_site}"
1211
- result = self._query(req)
1212
- if result["OK"]:
1213
- count = result["Value"][0][0]
1214
- else:
1215
- return S_ERROR("Failed to get Site data from the JobDB")
1216
- siteDict[site][status.replace('"', "")] = count
1217
- totalDict[status.replace('"', "")] += count
1218
-
1219
- siteDict["Total"] = totalDict
1220
- return S_OK(siteDict)
1221
-
1222
1193
  #################################################################################
1223
1194
  def getSiteSummaryWeb(self, selectDict, sortList, startItem, maxItems):
1224
1195
  """Get the summary of jobs in a given status on all the sites in the standard Web form"""
@@ -1,162 +1,33 @@
1
1
  from __future__ import annotations
2
2
 
3
- import base64
4
- import zlib
3
+ # Import stateless functions from DIRACCommon for backward compatibility
4
+ from DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils import *
5
5
 
6
6
  from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
7
- from DIRAC.Core.Utilities.DErrno import EWMSSUBM
8
7
  from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
9
- from DIRAC.Core.Utilities.ReturnValues import S_ERROR, S_OK, returnValueOrRaise
10
- from DIRAC.WorkloadManagementSystem.Client import JobStatus
11
- from DIRAC.WorkloadManagementSystem.Client.JobState.JobManifest import JobManifest
8
+ from DIRAC.Core.Utilities.ReturnValues import returnValueOrRaise
9
+ from DIRAC.WorkloadManagementSystem.Client.JobState.JobManifest import makeJobManifestConfig
12
10
 
13
11
  getDIRACPlatform = returnValueOrRaise(
14
12
  ObjectLoader().loadObject("ConfigurationSystem.Client.Helpers.Resources", "getDIRACPlatform")
15
13
  )
16
14
 
17
15
 
18
- def compressJDL(jdl):
19
- """Return compressed JDL string."""
20
- return base64.b64encode(zlib.compress(jdl.encode(), -1)).decode()
16
+ def checkAndPrepareJob(
17
+ jobID, classAdJob, classAdReq, owner, ownerGroup, jobAttrs, vo
18
+ ): # pylint: disable=function-redefined
19
+ from DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils import checkAndPrepareJob
21
20
 
21
+ config = {
22
+ "inputDataPolicyForVO": Operations(vo=vo).getValue("InputDataPolicy/InputDataModule"),
23
+ "softwareDistModuleForVO": Operations(vo=vo).getValue("SoftwareDistModule"),
24
+ "defaultCPUTimeForOwnerGroup": Operations(group=ownerGroup).getValue("JobDescription/DefaultCPUTime", 86400),
25
+ "getDIRACPlatform": getDIRACPlatform,
26
+ }
27
+ return checkAndPrepareJob(jobID, classAdJob, classAdReq, owner, ownerGroup, jobAttrs, vo, config=config)
22
28
 
23
- def extractJDL(compressedJDL):
24
- """Return decompressed JDL string."""
25
- # the starting bracket is guaranteeed by JobManager.submitJob
26
- # we need the check to be backward compatible
27
- if isinstance(compressedJDL, bytes):
28
- if compressedJDL.startswith(b"["):
29
- return compressedJDL.decode()
30
- else:
31
- if compressedJDL.startswith("["):
32
- return compressedJDL
33
- return zlib.decompress(base64.b64decode(compressedJDL)).decode()
34
29
 
30
+ def checkAndAddOwner(jdl: str, owner: str, ownerGroup: str): # pylint: disable=function-redefined
31
+ from DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils import checkAndAddOwner
35
32
 
36
- def checkAndAddOwner(jdl: str, owner: str, ownerGroup: str) -> JobManifest:
37
- jobManifest = JobManifest()
38
- res = jobManifest.load(jdl)
39
- if not res["OK"]:
40
- return res
41
-
42
- jobManifest.setOptionsFromDict({"Owner": owner, "OwnerGroup": ownerGroup})
43
- res = jobManifest.check()
44
- if not res["OK"]:
45
- return res
46
-
47
- return S_OK(jobManifest)
48
-
49
-
50
- def fixJDL(jdl: str) -> str:
51
- # 1.- insert original JDL on DB and get new JobID
52
- # Fix the possible lack of the brackets in the JDL
53
- if jdl.strip()[0].find("[") != 0:
54
- jdl = "[" + jdl + "]"
55
- return jdl
56
-
57
-
58
- def checkAndPrepareJob(jobID, classAdJob, classAdReq, owner, ownerGroup, jobAttrs, vo):
59
- error = ""
60
-
61
- jdlOwner = classAdJob.getAttributeString("Owner")
62
- jdlOwnerGroup = classAdJob.getAttributeString("OwnerGroup")
63
- jdlVO = classAdJob.getAttributeString("VirtualOrganization")
64
-
65
- # The below is commented out since this is always overwritten by the submitter IDs
66
- # but the check allows to findout inconsistent client environments
67
- if jdlOwner and jdlOwner != owner:
68
- error = "Wrong Owner in JDL"
69
- elif jdlOwnerGroup and jdlOwnerGroup != ownerGroup:
70
- error = "Wrong Owner Group in JDL"
71
- elif jdlVO and jdlVO != vo:
72
- error = "Wrong Virtual Organization in JDL"
73
-
74
- classAdJob.insertAttributeString("Owner", owner)
75
- classAdJob.insertAttributeString("OwnerGroup", ownerGroup)
76
-
77
- if vo:
78
- classAdJob.insertAttributeString("VirtualOrganization", vo)
79
-
80
- classAdReq.insertAttributeString("Owner", owner)
81
- classAdReq.insertAttributeString("OwnerGroup", ownerGroup)
82
- if vo:
83
- classAdReq.insertAttributeString("VirtualOrganization", vo)
84
-
85
- inputDataPolicy = Operations(vo=vo).getValue("InputDataPolicy/InputDataModule")
86
- if inputDataPolicy and not classAdJob.lookupAttribute("InputDataModule"):
87
- classAdJob.insertAttributeString("InputDataModule", inputDataPolicy)
88
-
89
- softwareDistModule = Operations(vo=vo).getValue("SoftwareDistModule")
90
- if softwareDistModule and not classAdJob.lookupAttribute("SoftwareDistModule"):
91
- classAdJob.insertAttributeString("SoftwareDistModule", softwareDistModule)
92
-
93
- # priority
94
- priority = classAdJob.getAttributeInt("Priority")
95
- if priority is None:
96
- priority = 0
97
- classAdReq.insertAttributeInt("UserPriority", priority)
98
-
99
- # CPU time
100
- cpuTime = classAdJob.getAttributeInt("CPUTime")
101
- if cpuTime is None:
102
- opsHelper = Operations(group=ownerGroup)
103
- cpuTime = opsHelper.getValue("JobDescription/DefaultCPUTime", 86400)
104
- classAdReq.insertAttributeInt("CPUTime", cpuTime)
105
-
106
- # platform(s)
107
- platformList = classAdJob.getListFromExpression("Platform")
108
- if platformList:
109
- result = getDIRACPlatform(platformList)
110
- if not result["OK"]:
111
- return result
112
- if result["Value"]:
113
- classAdReq.insertAttributeVectorString("Platforms", result["Value"])
114
- else:
115
- error = "OS compatibility info not found"
116
- if error:
117
- retVal = S_ERROR(EWMSSUBM, error)
118
- retVal["JobId"] = jobID
119
- retVal["Status"] = JobStatus.FAILED
120
- retVal["MinorStatus"] = error
121
-
122
- jobAttrs["Status"] = JobStatus.FAILED
123
-
124
- jobAttrs["MinorStatus"] = error
125
- return retVal
126
- return S_OK()
127
-
128
-
129
- def createJDLWithInitialStatus(
130
- classAdJob, classAdReq, jdl2DBParameters, jobAttrs, initialStatus, initialMinorStatus, *, modern=False
131
- ):
132
- """
133
- :param modern: if True, store boolean instead of string for VerifiedFlag (used by diracx only)
134
- """
135
- priority = classAdJob.getAttributeInt("Priority")
136
- if priority is None:
137
- priority = 0
138
- jobAttrs["UserPriority"] = priority
139
-
140
- for jdlName in jdl2DBParameters:
141
- # Defaults are set by the DB.
142
- jdlValue = classAdJob.getAttributeString(jdlName)
143
- if jdlValue:
144
- jobAttrs[jdlName] = jdlValue
145
-
146
- jdlValue = classAdJob.getAttributeString("Site")
147
- if jdlValue:
148
- if jdlValue.find(",") != -1:
149
- jobAttrs["Site"] = "Multiple"
150
- else:
151
- jobAttrs["Site"] = jdlValue
152
-
153
- jobAttrs["VerifiedFlag"] = True if modern else "True"
154
-
155
- jobAttrs["Status"] = initialStatus
156
-
157
- jobAttrs["MinorStatus"] = initialMinorStatus
158
-
159
- reqJDL = classAdReq.asJDL()
160
- classAdJob.insertAttributeInt("JobRequirements", reqJDL)
161
-
162
- return classAdJob.asJDL()
33
+ return checkAndAddOwner(jdl, owner, ownerGroup, job_manifest_config=makeJobManifestConfig(ownerGroup))
@@ -1,10 +1,10 @@
1
- """ Module containing a front-end to the OpenSearch-based JobParametersDB.
2
- This is a drop-in replacement for MySQL-based table JobDB.JobParameters.
1
+ """Module containing a front-end to the OpenSearch-based JobParametersDB.
2
+ This is a drop-in replacement for MySQL-based table JobDB.JobParameters.
3
3
 
4
- The following class methods are provided for public usage
5
- - getJobParameters()
6
- - setJobParameter()
7
- - deleteJobParameters()
4
+ The following class methods are provided for public usage
5
+ - getJobParameters()
6
+ - setJobParameter()
7
+ - deleteJobParameters()
8
8
  """
9
9
 
10
10
  from DIRAC import S_ERROR, S_OK
@@ -37,11 +37,11 @@ class JobParametersDB(ElasticDB):
37
37
  def __init__(self, parentLogger=None):
38
38
  """Standard Constructor"""
39
39
 
40
- self.fullname = "WorkloadManagement/ElasticJobParametersDB"
40
+ self.fullname = "WorkloadManagement/JobParametersDB"
41
41
  self.index_name = self.getCSOption("index_name", "job_parameters")
42
42
 
43
43
  try:
44
- # Connecting to the ES cluster
44
+ # Connecting to the OpenSearch cluster
45
45
  super().__init__(self.fullname, self.index_name, parentLogger=parentLogger)
46
46
  except Exception:
47
47
  RuntimeError("Can't connect to JobParameters index")
@@ -69,7 +69,7 @@ class JobParametersDB(ElasticDB):
69
69
  raise RuntimeError(result["Message"])
70
70
  self.log.always("Index created:", indexName)
71
71
 
72
- def getJobParameters(self, jobIDs: int | list[int], vo: str, paramList=None) -> dict:
72
+ def getJobParameters(self, jobIDs: int | list[int], vo: str, paramList: list[str] | None = None) -> dict:
73
73
  """Get Job Parameters defined for jobID.
74
74
  Returns a dictionary with the Job Parameters.
75
75
  If paramList is empty - all the parameters are returned.
@@ -447,9 +447,10 @@ AND SubmissionTime < DATE_SUB(UTC_TIMESTAMP(),INTERVAL %d DAY)"
447
447
  """Get IDs of Jobs that were executed by a pilot"""
448
448
  cmd = "SELECT pilotID,JobID FROM JobToPilotMapping "
449
449
  if isinstance(pilotID, list):
450
- cmd = cmd + " WHERE pilotID IN (%s)" % ",".join(["%s" % x for x in pilotID])
450
+ pilotIDs_string = ",".join(str(int(x)) for x in pilotID)
451
+ cmd = f"{cmd} WHERE pilotID IN ({pilotIDs_string})"
451
452
  else:
452
- cmd = cmd + f" WHERE pilotID = {pilotID}"
453
+ cmd = f"{cmd} WHERE pilotID = {pilotID}"
453
454
 
454
455
  result = self._query(cmd)
455
456
  if not result["OK"]:
@@ -1,6 +1,6 @@
1
1
  """ SandboxMetadataDB class is a front-end to the metadata for sandboxes
2
2
  """
3
- from DIRAC import S_ERROR, S_OK, gLogger
3
+ from DIRAC import S_ERROR, S_OK
4
4
  from DIRAC.ConfigurationSystem.Client.Helpers import Registry
5
5
  from DIRAC.Core.Base.DB import DB
6
6
  from DIRAC.Core.Security import Properties
@@ -64,7 +64,7 @@ class SandboxMetadataDB(DB):
64
64
  "Type": "VARCHAR(64) NOT NULL",
65
65
  },
66
66
  "Indexes": {"Entity": ["EntityId"], "SBIndex": ["SBId"]},
67
- "UniqueIndexes": {"Mapping": ["SBId", "EntityId", "Type"]},
67
+ "PrimaryKey": ["SBId", "EntityId", "Type"],
68
68
  }
69
69
 
70
70
  for tableName in self.__tablesDesc:
@@ -220,48 +220,22 @@ class SandboxMetadataDB(DB):
220
220
  return result
221
221
  return S_OK(assigned)
222
222
 
223
- def __entitiesByRequesterCond(self, requesterName, requesterGroup):
224
- sqlCond = []
225
- requesterProps = Registry.getPropertiesForEntity(requesterGroup, name=requesterName)
226
- if Properties.JOB_ADMINISTRATOR in requesterProps:
227
- # Do nothing, just ensure it doesn't fit in the other cases
228
- pass
229
- elif Properties.JOB_SHARING in requesterProps:
230
- sqlCond.append(f"o.OwnerGroup='{requesterGroup}'")
231
- elif Properties.NORMAL_USER in requesterProps:
232
- sqlCond.append(f"o.OwnerGroup='{requesterGroup}'")
233
- sqlCond.append(f"o.Owner='{requesterName}'")
234
- else:
235
- return S_ERROR("Not authorized to access sandbox")
236
- return sqlCond
237
-
238
223
  @convertToReturnValue
239
- def unassignEntities(self, entities, requesterName, requesterGroup):
224
+ def unassignEntities(self, entities: list):
240
225
  """
241
- Unassign jobs to sandboxes
226
+ Unassign entities to sandboxes. Entities are a list of strings, e.g. ['job:1234', 'job:5678'].
242
227
 
243
228
  :param list entities: list of entities to unassign
244
229
  """
245
230
  if not entities:
246
231
  return None
247
- conds = self.__entitiesByRequesterCond(requesterName, requesterGroup)
248
232
 
249
- sqlCmd = "CREATE TEMPORARY TABLE to_delete_EntityId (EntityId VARCHAR(128) NOT NULL, PRIMARY KEY (EntityId)) ENGINE=MEMORY;"
233
+ sqlCmd = "CREATE TEMPORARY TABLE to_delete_EntityId (EntityId VARCHAR(128) NOT NULL, PRIMARY KEY (EntityId)) ENGINE=MEMORY;"
250
234
  returnValueOrRaise(self._update(sqlCmd))
251
235
  try:
252
236
  sqlCmd = "INSERT INTO to_delete_EntityId (EntityId) VALUES ( %s )"
253
237
  returnValueOrRaise(self._updatemany(sqlCmd, [(e,) for e in entities]))
254
238
  sqlCmd = "DELETE m from `sb_EntityMapping` m JOIN to_delete_EntityId t USING (EntityId)"
255
- if conds:
256
- sqlCmd = " ".join(
257
- [
258
- sqlCmd,
259
- "JOIN `sb_SandBoxes` s ON s.SBId = m.SBId",
260
- "JOIN `sb_Owners` o ON s.OwnerId = o.OwnerId",
261
- "WHERE",
262
- " AND ".join(conds),
263
- ]
264
- )
265
239
  returnValueOrRaise(self._update(sqlCmd))
266
240
  finally:
267
241
  sqlCmd = "DROP TEMPORARY TABLE to_delete_EntityId"
@@ -309,19 +283,34 @@ class SandboxMetadataDB(DB):
309
283
  "TIMESTAMPDIFF( DAY, LastAccessTime, UTC_TIMESTAMP() ) >= %d" % self.__assignedSBGraceDays,
310
284
  f"! Assigned AND TIMESTAMPDIFF( DAY, LastAccessTime, UTC_TIMESTAMP() ) >= {self.__unassignedSBGraceDays}",
311
285
  ]
312
- sqlCmd = f"SELECT SBId, SEName, SEPFN FROM `sb_SandBoxes` WHERE ( {' ) OR ( '.join(sqlCond)} )"
286
+ # Exclude sandboxes that are in S3 as those are handled by DiracX
287
+ sqlCmd = f"SELECT SBId, SEName, SEPFN FROM `sb_SandBoxes` WHERE SEPFN not like '/S3/%' AND (( {' ) OR ( '.join(sqlCond)} ))"
313
288
  return self._query(sqlCmd)
314
289
 
290
+ @convertToReturnValue
315
291
  def deleteSandboxes(self, SBIdList):
316
292
  """
317
- Delete sandboxes
293
+ Delete sandboxes using a temporary table for efficiency and consistency.
318
294
  """
319
- sqlSBList = ", ".join([str(sbid) for sbid in SBIdList])
320
- for table in ("sb_SandBoxes", "sb_EntityMapping"):
321
- sqlCmd = f"DELETE FROM `{table}` WHERE SBId IN ( {sqlSBList} )"
322
- result = self._update(sqlCmd)
323
- if not result["OK"]:
324
- return result
295
+ if not SBIdList:
296
+ return S_OK()
297
+ # Create temporary table
298
+ sqlCmd = "CREATE TEMPORARY TABLE to_delete_SBId (SBId INTEGER(10) UNSIGNED NOT NULL, PRIMARY KEY (SBId)) ENGINE=MEMORY;"
299
+ returnValueOrRaise(self._update(sqlCmd))
300
+ try:
301
+ # Insert SBIds into temporary table
302
+ sqlCmd = "INSERT INTO to_delete_SBId (SBId) VALUES (%s)"
303
+ returnValueOrRaise(self._updatemany(sqlCmd, [(sbid,) for sbid in SBIdList]))
304
+ # Delete from sb_EntityMapping first (to respect FK constraints if any)
305
+ sqlCmd = "DELETE FROM `sb_EntityMapping` WHERE SBId IN (SELECT SBId FROM to_delete_SBId)"
306
+ returnValueOrRaise(self._update(sqlCmd))
307
+ # Delete from sb_SandBoxes
308
+ sqlCmd = "DELETE FROM `sb_SandBoxes` WHERE SBId IN (SELECT SBId FROM to_delete_SBId)"
309
+ returnValueOrRaise(self._update(sqlCmd))
310
+ finally:
311
+ # Drop temporary table
312
+ sqlCmd = "DROP TEMPORARY TABLE to_delete_SBId"
313
+ returnValueOrRaise(self._update(sqlCmd))
325
314
  return S_OK()
326
315
 
327
316
  def getSandboxId(self, SEName, SEPFN, requesterName, requesterGroup, field="SBId"):
@@ -0,0 +1,125 @@
1
+ from DIRAC import S_ERROR, S_OK, gLogger
2
+ from DIRAC.StorageManagementSystem.DB.StorageManagementDB import StorageManagementDB
3
+ from DIRAC.WorkloadManagementSystem.Client import JobStatus
4
+ from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
5
+ from DIRAC.WorkloadManagementSystem.DB.PilotAgentsDB import PilotAgentsDB
6
+ from DIRAC.WorkloadManagementSystem.DB.TaskQueueDB import TaskQueueDB
7
+ from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_DELETE, RIGHT_KILL
8
+ from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import _filterJobStateTransition
9
+
10
+
11
+ def _deleteJob(jobID, force=False):
12
+ """Set the job status to "Deleted"
13
+ and remove the pilot that ran and its logging info if the pilot is finished.
14
+
15
+ :param int jobID: job ID
16
+ :return: S_OK()/S_ERROR()
17
+ """
18
+ if not (result := JobDB().setJobStatus(jobID, JobStatus.DELETED, "Checking accounting", force=force))["OK"]:
19
+ gLogger.warn("Failed to set job Deleted status", result["Message"])
20
+ return result
21
+
22
+ if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
23
+ gLogger.warn("Failed to delete job from the TaskQueue")
24
+
25
+ # if it was the last job for the pilot
26
+ result = PilotAgentsDB().getPilotsForJobID(jobID)
27
+ if not result["OK"]:
28
+ gLogger.error("Failed to get Pilots for JobID", result["Message"])
29
+ return result
30
+ for pilot in result["Value"]:
31
+ res = PilotAgentsDB().getJobsForPilot(pilot)
32
+ if not res["OK"]:
33
+ gLogger.error("Failed to get jobs for pilot", res["Message"])
34
+ return res
35
+ if not res["Value"]: # if list of jobs for pilot is empty, delete pilot
36
+ result = PilotAgentsDB().getPilotInfo(pilotID=pilot)
37
+ if not result["OK"]:
38
+ gLogger.error("Failed to get pilot info", result["Message"])
39
+ return result
40
+ ret = PilotAgentsDB().deletePilot(result["Value"]["PilotJobReference"])
41
+ if not ret["OK"]:
42
+ gLogger.error("Failed to delete pilot from PilotAgentsDB", ret["Message"])
43
+ return ret
44
+
45
+ return S_OK()
46
+
47
+
48
+ def _killJob(jobID, sendKillCommand=True, force=False):
49
+ """Kill one job
50
+
51
+ :param int jobID: job ID
52
+ :param bool sendKillCommand: send kill command
53
+
54
+ :return: S_OK()/S_ERROR()
55
+ """
56
+ if sendKillCommand:
57
+ if not (result := JobDB().setJobCommand(jobID, "Kill"))["OK"]:
58
+ gLogger.warn("Failed to set job Kill command", result["Message"])
59
+ return result
60
+
61
+ gLogger.info("Job marked for termination", jobID)
62
+ if not (result := JobDB().setJobStatus(jobID, JobStatus.KILLED, "Marked for termination", force=force))["OK"]:
63
+ gLogger.warn("Failed to set job Killed status", result["Message"])
64
+ if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
65
+ gLogger.warn("Failed to delete job from the TaskQueue", result["Message"])
66
+
67
+ return S_OK()
68
+
69
+
70
+ def kill_delete_jobs(right, validJobList, nonauthJobList=[], force=False):
71
+ """Kill (== set the status to "KILLED") or delete (== set the status to "DELETED") jobs as necessary
72
+
73
+ :param str right: RIGHT_KILL or RIGHT_DELETE
74
+
75
+ :return: S_OK()/S_ERROR()
76
+ """
77
+ badIDs = []
78
+
79
+ killJobList = []
80
+ deleteJobList = []
81
+ if validJobList:
82
+ result = JobDB().getJobsAttributes(validJobList, ["Status"])
83
+ if not result["OK"]:
84
+ return result
85
+ jobStates = result["Value"]
86
+
87
+ # Get the jobs allowed to transition to the Killed state
88
+ killJobList.extend(_filterJobStateTransition(jobStates, JobStatus.KILLED))
89
+
90
+ if right == RIGHT_DELETE:
91
+ # Get the jobs allowed to transition to the Deleted state
92
+ deleteJobList.extend(_filterJobStateTransition(jobStates, JobStatus.DELETED))
93
+
94
+ for jobID in killJobList:
95
+ result = _killJob(jobID, force=force)
96
+ if not result["OK"]:
97
+ badIDs.append(jobID)
98
+
99
+ for jobID in deleteJobList:
100
+ result = _deleteJob(jobID, force=force)
101
+ if not result["OK"]:
102
+ badIDs.append(jobID)
103
+
104
+ # Look for jobs that are in the Staging state to send kill signal to the stager
105
+ stagingJobList = [jobID for jobID, sDict in jobStates.items() if sDict["Status"] == JobStatus.STAGING]
106
+
107
+ if stagingJobList:
108
+ stagerDB = StorageManagementDB()
109
+ gLogger.info("Going to send killing signal to stager as well!")
110
+ result = stagerDB.killTasksBySourceTaskID(stagingJobList)
111
+ if not result["OK"]:
112
+ gLogger.warn("Failed to kill some Stager tasks", result["Message"])
113
+
114
+ if nonauthJobList or badIDs:
115
+ result = S_ERROR("Some jobs failed deletion")
116
+ if nonauthJobList:
117
+ gLogger.warn("Non-authorized JobIDs won't be deleted", str(nonauthJobList))
118
+ result["NonauthorizedJobIDs"] = nonauthJobList
119
+ if badIDs:
120
+ gLogger.warn("JobIDs failed to be deleted", str(badIDs))
121
+ result["FailedJobIDs"] = badIDs
122
+ return result
123
+
124
+ jobsList = killJobList if right == RIGHT_KILL else deleteJobList
125
+ return S_OK(jobsList)
@@ -28,7 +28,7 @@ def test_getInputData(jobDB: JobDB):
28
28
  """Test the getInputData method from JobDB"""
29
29
  # Arrange
30
30
  jobDB._escapeString = MagicMock(return_value=S_OK())
31
- jobDB._query = MagicMock(return_value=S_OK((("/vo/user/lfn1",), ("LFN:/vo/user/lfn2",))))
31
+ jobDB._query = MagicMock(return_value=S_OK([(1234, "/vo/user/lfn1"), (1234, "LFN:/vo/user/lfn2")]))
32
32
 
33
33
  # Act
34
34
  res = jobDB.getInputData(1234)
@@ -0,0 +1,28 @@
1
+ """ unit test (pytest) of JobAdministration module
2
+ """
3
+
4
+ from unittest.mock import MagicMock
5
+
6
+ import pytest
7
+
8
+ # sut
9
+ from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ "jobIDs_list, right",
14
+ [
15
+ ([], "Kill"),
16
+ ([], "Delete"),
17
+ (1, "Kill"),
18
+ ([1, 2], "Kill"),
19
+ ],
20
+ )
21
+ def test___kill_delete_jobs(mocker, jobIDs_list, right):
22
+ mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.JobDB", MagicMock())
23
+ mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.TaskQueueDB", MagicMock())
24
+ mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.PilotAgentsDB", MagicMock())
25
+ mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.StorageManagementDB", MagicMock())
26
+
27
+ res = kill_delete_jobs(right, jobIDs_list)
28
+ assert res["OK"]