DIRAC 9.0.0a42__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 (236) 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 +38 -26
  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 +32 -19
  14. DIRAC/ConfigurationSystem/Client/test/Test_PathFinder.py +41 -1
  15. DIRAC/ConfigurationSystem/private/RefresherBase.py +4 -2
  16. DIRAC/Core/Base/API.py +4 -7
  17. DIRAC/Core/Base/SQLAlchemyDB.py +1 -0
  18. DIRAC/Core/DISET/ServiceReactor.py +11 -3
  19. DIRAC/Core/DISET/private/BaseClient.py +1 -2
  20. DIRAC/Core/DISET/private/Transports/M2SSLTransport.py +9 -7
  21. DIRAC/Core/DISET/private/Transports/SSL/M2Utils.py +3 -1
  22. DIRAC/Core/LCG/GOCDBClient.py +5 -7
  23. DIRAC/Core/Security/DiracX.py +31 -17
  24. DIRAC/Core/Security/IAMService.py +5 -10
  25. DIRAC/Core/Security/Locations.py +27 -18
  26. DIRAC/Core/Security/ProxyInfo.py +9 -5
  27. DIRAC/Core/Security/VOMSService.py +2 -4
  28. DIRAC/Core/Security/m2crypto/X509Certificate.py +4 -6
  29. DIRAC/Core/Security/m2crypto/asn1_utils.py +17 -5
  30. DIRAC/Core/Security/test/test_diracx_token_from_pem.py +161 -0
  31. DIRAC/Core/Tornado/Client/ClientSelector.py +4 -1
  32. DIRAC/Core/Tornado/Server/TornadoService.py +1 -1
  33. DIRAC/Core/Utilities/CGroups2.py +328 -0
  34. DIRAC/Core/Utilities/ClassAd/ClassAdLight.py +4 -290
  35. DIRAC/Core/Utilities/DErrno.py +5 -309
  36. DIRAC/Core/Utilities/Extensions.py +10 -1
  37. DIRAC/Core/Utilities/File.py +1 -1
  38. DIRAC/Core/Utilities/Graphs/GraphData.py +1 -1
  39. DIRAC/Core/Utilities/Graphs/GraphUtilities.py +6 -1
  40. DIRAC/Core/Utilities/JDL.py +1 -195
  41. DIRAC/Core/Utilities/List.py +1 -124
  42. DIRAC/Core/Utilities/MySQL.py +103 -99
  43. DIRAC/Core/Utilities/Os.py +32 -1
  44. DIRAC/Core/Utilities/Platform.py +2 -107
  45. DIRAC/Core/Utilities/Proxy.py +0 -4
  46. DIRAC/Core/Utilities/ReturnValues.py +7 -252
  47. DIRAC/Core/Utilities/StateMachine.py +12 -178
  48. DIRAC/Core/Utilities/Subprocess.py +35 -14
  49. DIRAC/Core/Utilities/TimeUtilities.py +10 -253
  50. DIRAC/Core/Utilities/test/Test_JDL.py +0 -3
  51. DIRAC/Core/Utilities/test/Test_Profiler.py +20 -20
  52. DIRAC/Core/scripts/dirac_agent.py +1 -1
  53. DIRAC/Core/scripts/dirac_apptainer_exec.py +72 -46
  54. DIRAC/Core/scripts/dirac_configure.py +1 -3
  55. DIRAC/Core/scripts/dirac_install_db.py +24 -6
  56. DIRAC/Core/scripts/dirac_platform.py +1 -92
  57. DIRAC/DataManagementSystem/Agent/FTS3Agent.py +8 -7
  58. DIRAC/DataManagementSystem/Agent/RequestOperations/RemoveFile.py +7 -6
  59. DIRAC/DataManagementSystem/Client/FTS3Job.py +71 -34
  60. DIRAC/DataManagementSystem/DB/FTS3DB.py +7 -3
  61. DIRAC/DataManagementSystem/DB/FileCatalogComponents/DatasetManager/DatasetManager.py +1 -1
  62. DIRAC/DataManagementSystem/DB/FileCatalogDB.sql +9 -9
  63. DIRAC/DataManagementSystem/DB/FileCatalogWithFkAndPsDB.sql +9 -9
  64. DIRAC/DataManagementSystem/Utilities/DMSHelpers.py +6 -2
  65. DIRAC/DataManagementSystem/scripts/dirac_admin_allow_se.py +13 -8
  66. DIRAC/DataManagementSystem/scripts/dirac_admin_ban_se.py +13 -8
  67. DIRAC/DataManagementSystem/scripts/dirac_dms_create_moving_request.py +2 -0
  68. DIRAC/DataManagementSystem/scripts/dirac_dms_protocol_matrix.py +0 -1
  69. DIRAC/FrameworkSystem/Client/BundleDeliveryClient.py +2 -7
  70. DIRAC/FrameworkSystem/Client/ComponentInstaller.py +9 -4
  71. DIRAC/FrameworkSystem/Client/ProxyManagerClient.py +5 -2
  72. DIRAC/FrameworkSystem/Client/SystemAdministratorClientCLI.py +11 -6
  73. DIRAC/FrameworkSystem/ConfigTemplate.cfg +2 -0
  74. DIRAC/FrameworkSystem/DB/AuthDB.py +3 -3
  75. DIRAC/FrameworkSystem/DB/InstalledComponentsDB.py +4 -4
  76. DIRAC/FrameworkSystem/DB/ProxyDB.py +11 -3
  77. DIRAC/FrameworkSystem/DB/TokenDB.py +1 -1
  78. DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py +8 -6
  79. DIRAC/FrameworkSystem/Utilities/MonitoringUtilities.py +2 -19
  80. DIRAC/FrameworkSystem/Utilities/TokenManagementUtilities.py +3 -2
  81. DIRAC/FrameworkSystem/Utilities/diracx.py +36 -14
  82. DIRAC/FrameworkSystem/private/authorization/AuthServer.py +2 -2
  83. DIRAC/FrameworkSystem/scripts/dirac_admin_update_pilot.py +18 -11
  84. DIRAC/FrameworkSystem/scripts/dirac_login.py +2 -2
  85. DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +7 -8
  86. DIRAC/Interfaces/API/Dirac.py +27 -15
  87. DIRAC/Interfaces/API/DiracAdmin.py +45 -17
  88. DIRAC/Interfaces/API/Job.py +9 -13
  89. DIRAC/Interfaces/scripts/dirac_admin_allow_site.py +12 -18
  90. DIRAC/Interfaces/scripts/dirac_admin_ban_site.py +12 -10
  91. DIRAC/Interfaces/scripts/dirac_admin_get_site_mask.py +4 -13
  92. DIRAC/Interfaces/scripts/dirac_admin_reset_job.py +3 -6
  93. DIRAC/Interfaces/scripts/dirac_wms_job_parameters.py +0 -1
  94. DIRAC/MonitoringSystem/Client/Types/WMSHistory.py +4 -0
  95. DIRAC/MonitoringSystem/Client/WebAppClient.py +26 -0
  96. DIRAC/MonitoringSystem/ConfigTemplate.cfg +9 -0
  97. DIRAC/MonitoringSystem/DB/MonitoringDB.py +6 -25
  98. DIRAC/MonitoringSystem/Service/MonitoringHandler.py +0 -33
  99. DIRAC/MonitoringSystem/Service/WebAppHandler.py +599 -0
  100. DIRAC/MonitoringSystem/private/MainReporter.py +0 -3
  101. DIRAC/ProductionSystem/DB/ProductionDB.sql +4 -4
  102. DIRAC/ProductionSystem/scripts/dirac_prod_get.py +2 -2
  103. DIRAC/ProductionSystem/scripts/dirac_prod_get_all.py +2 -2
  104. DIRAC/ProductionSystem/scripts/dirac_prod_get_trans.py +2 -3
  105. DIRAC/RequestManagementSystem/Agent/RequestExecutingAgent.py +8 -6
  106. DIRAC/RequestManagementSystem/Agent/RequestOperations/ForwardDISET.py +2 -14
  107. DIRAC/RequestManagementSystem/Client/ReqClient.py +66 -13
  108. DIRAC/RequestManagementSystem/ConfigTemplate.cfg +6 -6
  109. DIRAC/RequestManagementSystem/DB/RequestDB.py +10 -5
  110. DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
  111. DIRAC/RequestManagementSystem/private/RequestValidator.py +40 -46
  112. DIRAC/ResourceStatusSystem/Client/SiteStatus.py +4 -2
  113. DIRAC/ResourceStatusSystem/Command/FreeDiskSpaceCommand.py +3 -1
  114. DIRAC/ResourceStatusSystem/DB/ResourceManagementDB.py +8 -8
  115. DIRAC/ResourceStatusSystem/DB/ResourceStatusDB.py +2 -2
  116. DIRAC/ResourceStatusSystem/Utilities/CSHelpers.py +2 -31
  117. DIRAC/ResourceStatusSystem/scripts/dirac_rss_set_status.py +30 -12
  118. DIRAC/Resources/Catalog/RucioFileCatalogClient.py +195 -1
  119. DIRAC/Resources/Catalog/test/Test_RucioFileCatalogClient.py +181 -0
  120. DIRAC/Resources/Computing/AREXComputingElement.py +25 -8
  121. DIRAC/Resources/Computing/BatchSystems/Condor.py +126 -108
  122. DIRAC/Resources/Computing/BatchSystems/SLURM.py +5 -1
  123. DIRAC/Resources/Computing/BatchSystems/test/Test_SLURM.py +46 -0
  124. DIRAC/Resources/Computing/ComputingElement.py +1 -1
  125. DIRAC/Resources/Computing/HTCondorCEComputingElement.py +44 -44
  126. DIRAC/Resources/Computing/InProcessComputingElement.py +4 -2
  127. DIRAC/Resources/Computing/LocalComputingElement.py +1 -18
  128. DIRAC/Resources/Computing/SSHBatchComputingElement.py +1 -17
  129. DIRAC/Resources/Computing/SSHComputingElement.py +1 -18
  130. DIRAC/Resources/Computing/SingularityComputingElement.py +19 -5
  131. DIRAC/Resources/Computing/test/Test_HTCondorCEComputingElement.py +67 -49
  132. DIRAC/Resources/Computing/test/Test_PoolComputingElement.py +2 -1
  133. DIRAC/Resources/IdProvider/CheckInIdProvider.py +13 -0
  134. DIRAC/Resources/IdProvider/IdProviderFactory.py +11 -3
  135. DIRAC/Resources/MessageQueue/StompMQConnector.py +1 -1
  136. DIRAC/Resources/Storage/GFAL2_StorageBase.py +24 -15
  137. DIRAC/Resources/Storage/OccupancyPlugins/WLCGAccountingHTTPJson.py +1 -3
  138. DIRAC/Resources/Storage/StorageBase.py +4 -2
  139. DIRAC/Resources/Storage/StorageElement.py +6 -7
  140. DIRAC/StorageManagementSystem/DB/StorageManagementDB.sql +2 -2
  141. DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +10 -16
  142. DIRAC/TransformationSystem/Agent/TransformationAgent.py +22 -1
  143. DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +16 -16
  144. DIRAC/TransformationSystem/Client/TaskManager.py +2 -4
  145. DIRAC/TransformationSystem/Client/Transformation.py +6 -7
  146. DIRAC/TransformationSystem/Client/TransformationClient.py +21 -11
  147. DIRAC/TransformationSystem/Client/Utilities.py +9 -0
  148. DIRAC/TransformationSystem/DB/TransformationDB.py +11 -14
  149. DIRAC/TransformationSystem/DB/TransformationDB.sql +9 -9
  150. DIRAC/TransformationSystem/Service/TransformationManagerHandler.py +0 -333
  151. DIRAC/TransformationSystem/Utilities/ReplicationCLIParameters.py +3 -3
  152. DIRAC/TransformationSystem/Utilities/TransformationInfo.py +7 -5
  153. DIRAC/TransformationSystem/scripts/dirac_production_runjoblocal.py +2 -4
  154. DIRAC/TransformationSystem/test/Test_TransformationInfo.py +22 -15
  155. DIRAC/TransformationSystem/test/Test_replicationTransformation.py +5 -6
  156. DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
  157. DIRAC/WorkloadManagementSystem/Agent/JobAgent.py +38 -26
  158. DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +12 -8
  159. DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py +4 -3
  160. DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py +13 -13
  161. DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +18 -14
  162. DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +18 -51
  163. DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py +41 -1
  164. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +45 -4
  165. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobCleaningAgent.py +7 -9
  166. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PushJobAgent.py +1 -0
  167. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_SiteDirector.py +9 -2
  168. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +4 -5
  169. DIRAC/WorkloadManagementSystem/Client/DownloadInputData.py +9 -9
  170. DIRAC/WorkloadManagementSystem/Client/InputDataResolution.py +6 -6
  171. DIRAC/WorkloadManagementSystem/Client/JobMonitoringClient.py +10 -11
  172. DIRAC/WorkloadManagementSystem/Client/JobReport.py +1 -1
  173. DIRAC/WorkloadManagementSystem/Client/JobState/CachedJobState.py +3 -0
  174. DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
  175. DIRAC/WorkloadManagementSystem/Client/JobState/JobState.py +6 -0
  176. DIRAC/WorkloadManagementSystem/Client/JobStateUpdateClient.py +3 -0
  177. DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -152
  178. DIRAC/WorkloadManagementSystem/Client/PoolXMLSlice.py +12 -19
  179. DIRAC/WorkloadManagementSystem/Client/SandboxStoreClient.py +25 -38
  180. DIRAC/WorkloadManagementSystem/Client/WMSClient.py +2 -3
  181. DIRAC/WorkloadManagementSystem/Client/test/Test_Client_DownloadInputData.py +29 -0
  182. DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg +4 -8
  183. DIRAC/WorkloadManagementSystem/DB/JobDB.py +89 -132
  184. DIRAC/WorkloadManagementSystem/DB/JobDB.sql +8 -8
  185. DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
  186. DIRAC/WorkloadManagementSystem/DB/JobLoggingDB.py +19 -6
  187. DIRAC/WorkloadManagementSystem/DB/JobParametersDB.py +9 -9
  188. DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.py +16 -5
  189. DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.sql +3 -3
  190. DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +44 -82
  191. DIRAC/WorkloadManagementSystem/DB/StatusUtils.py +125 -0
  192. DIRAC/WorkloadManagementSystem/DB/tests/Test_JobDB.py +1 -1
  193. DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py +28 -0
  194. DIRAC/WorkloadManagementSystem/Executor/JobSanity.py +5 -4
  195. DIRAC/WorkloadManagementSystem/Executor/JobScheduling.py +4 -0
  196. DIRAC/WorkloadManagementSystem/FutureClient/JobStateUpdateClient.py +75 -33
  197. DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +22 -11
  198. DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapperTemplate.py +9 -10
  199. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +60 -10
  200. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py +4 -0
  201. DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +33 -154
  202. DIRAC/WorkloadManagementSystem/Service/JobMonitoringHandler.py +5 -323
  203. DIRAC/WorkloadManagementSystem/Service/JobStateUpdateHandler.py +0 -16
  204. DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py +6 -103
  205. DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +7 -53
  206. DIRAC/WorkloadManagementSystem/Service/WMSAdministratorHandler.py +16 -79
  207. DIRAC/WorkloadManagementSystem/Service/WMSUtilities.py +4 -18
  208. DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -209
  209. DIRAC/WorkloadManagementSystem/Utilities/JobParameters.py +65 -3
  210. DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py +2 -64
  211. DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py +7 -171
  212. DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py +73 -7
  213. DIRAC/WorkloadManagementSystem/Utilities/PilotWrapper.py +41 -11
  214. DIRAC/WorkloadManagementSystem/Utilities/RemoteRunner.py +16 -0
  215. DIRAC/WorkloadManagementSystem/Utilities/Utils.py +36 -1
  216. DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py +15 -0
  217. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py +1 -15
  218. DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
  219. DIRAC/WorkloadManagementSystem/Utilities/test/Test_PilotWrapper.py +16 -0
  220. DIRAC/WorkloadManagementSystem/scripts/dirac_jobexec.py +7 -2
  221. DIRAC/WorkloadManagementSystem/scripts/dirac_wms_pilot_job_info.py +1 -1
  222. DIRAC/__init__.py +62 -60
  223. DIRAC/tests/Utilities/testJobDefinitions.py +22 -28
  224. {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/METADATA +8 -5
  225. {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/RECORD +229 -228
  226. {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/WHEEL +1 -1
  227. {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/entry_points.txt +0 -3
  228. DIRAC/Core/Utilities/test/Test_List.py +0 -150
  229. DIRAC/Core/Utilities/test/Test_Time.py +0 -88
  230. DIRAC/Resources/Computing/PilotBundle.py +0 -70
  231. DIRAC/TransformationSystem/scripts/dirac_transformation_archive.py +0 -30
  232. DIRAC/TransformationSystem/scripts/dirac_transformation_clean.py +0 -30
  233. DIRAC/TransformationSystem/scripts/dirac_transformation_remove_output.py +0 -30
  234. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobManager.py +0 -58
  235. {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info/licenses}/LICENSE +0 -0
  236. {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/top_level.txt +0 -0
@@ -6,9 +6,8 @@ issuer with a duration of 1 day.
6
6
  """
7
7
  from datetime import datetime, timedelta
8
8
 
9
- from DIRAC import S_OK
9
+ from DIRAC import S_OK, gLogger
10
10
  from DIRAC import exit as DIRACExit
11
- from DIRAC import gLogger
12
11
  from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
13
12
  from DIRAC.Core.Base.Script import Script
14
13
  from DIRAC.Core.Security.ProxyInfo import getProxyInfo
@@ -29,6 +28,8 @@ def registerSwitches():
29
28
  ("status=", "Status to be changed"),
30
29
  ("reason=", "Reason to set the Status"),
31
30
  ("VO=", "VO to change a status for. When omitted, status will be changed for all VOs"),
31
+ ("tokenOwner=", "Owner of the token"),
32
+ ("days=", "Number of days the token is valid for. Default is 1 day. 0 or less days denotes forever."),
32
33
  )
33
34
 
34
35
  for switch in switches:
@@ -50,6 +51,7 @@ def parseSwitches():
50
51
  switches = dict(Script.getUnprocessedSwitches())
51
52
  switches.setdefault("statusType", None)
52
53
  switches.setdefault("VO", None)
54
+ switches.setdefault("days", 1)
53
55
 
54
56
  for key in ("element", "name", "status", "reason"):
55
57
  if key not in switches:
@@ -136,6 +138,9 @@ def unpack(switchDict):
136
138
  switchDictClone["statusType"] = None
137
139
  switchDictSet.append(switchDictClone)
138
140
 
141
+ for sd in switchDictSet:
142
+ sd.update({"tokenOwner": switchDict.get("tokenOwner")})
143
+
139
144
  return switchDictSet
140
145
 
141
146
 
@@ -180,7 +185,11 @@ def setStatus(switchDict, tokenOwner):
180
185
  )
181
186
  return S_OK()
182
187
 
183
- tomorrow = datetime.utcnow().replace(microsecond=0) + timedelta(days=1)
188
+ tokenLifetime = int(switchDict["days"])
189
+ if tokenLifetime <= 0:
190
+ tokenExpiration = datetime.max
191
+ else:
192
+ tokenExpiration = datetime.utcnow().replace(microsecond=0) + timedelta(days=tokenLifetime)
184
193
 
185
194
  for status, statusType in elements:
186
195
  gLogger.debug(f"{status} {statusType}")
@@ -190,8 +199,16 @@ def setStatus(switchDict, tokenOwner):
190
199
  continue
191
200
 
192
201
  gLogger.debug(
193
- "About to set status %s -> %s for %s, statusType: %s, VO: %s, reason: %s"
194
- % (status, switchDict["status"], switchDict["name"], statusType, switchDict["VO"], switchDict["reason"])
202
+ "About to set status %s -> %s for %s, statusType: %s, VO: %s, reason: %s, days: %s"
203
+ % (
204
+ status,
205
+ switchDict["status"],
206
+ switchDict["name"],
207
+ statusType,
208
+ switchDict["VO"],
209
+ switchDict["reason"],
210
+ switchDict["days"],
211
+ )
195
212
  )
196
213
  result = rssClient.modifyStatusElement(
197
214
  switchDict["element"],
@@ -202,7 +219,7 @@ def setStatus(switchDict, tokenOwner):
202
219
  reason=switchDict["reason"],
203
220
  vO=switchDict["VO"],
204
221
  tokenOwner=tokenOwner,
205
- tokenExpiration=tomorrow,
222
+ tokenExpiration=tokenExpiration,
206
223
  )
207
224
  if not result["OK"]:
208
225
  return result
@@ -215,14 +232,15 @@ def run(switchDict):
215
232
  Main function of the script
216
233
  """
217
234
 
218
- tokenOwner = getTokenOwner()
219
- if not tokenOwner["OK"]:
220
- gLogger.error(tokenOwner["Message"])
221
- DIRACExit(1)
222
- tokenOwner = tokenOwner["Value"]
235
+ tokenOwner = switchDict.get("tokenOwner")
236
+ if tokenOwner is None:
237
+ tokenOwner = getTokenOwner()
238
+ if not tokenOwner["OK"]:
239
+ gLogger.error(tokenOwner["Message"])
240
+ DIRACExit(1)
241
+ tokenOwner = tokenOwner["Value"]
223
242
 
224
243
  gLogger.notice(f"TokenOwner is {tokenOwner}")
225
-
226
244
  result = setStatus(switchDict, tokenOwner)
227
245
  if not result["OK"]:
228
246
  gLogger.error(result["Message"])
@@ -60,6 +60,8 @@ class RucioFileCatalogClient(FileCatalogClientBase):
60
60
  "resolveDataset",
61
61
  "getLFNForPFN",
62
62
  "getUserDirectory",
63
+ "getFileUserMetadata",
64
+ "findFilesByMetadata",
63
65
  ]
64
66
 
65
67
  WRITE_METHODS = FileCatalogClientBase.WRITE_METHODS + [
@@ -78,6 +80,7 @@ class RucioFileCatalogClient(FileCatalogClientBase):
78
80
  "createDataset",
79
81
  "changePathOwner",
80
82
  "changePathMode",
83
+ "setMetadata",
81
84
  ]
82
85
 
83
86
  NO_LFN_METHODS = FileCatalogClientBase.NO_LFN_METHODS + [
@@ -85,6 +88,7 @@ class RucioFileCatalogClient(FileCatalogClientBase):
85
88
  "createUserDirectory",
86
89
  "createUserMapping",
87
90
  "removeUserDirectory",
91
+ "findFilesByMetadata",
88
92
  ]
89
93
 
90
94
  ADMIN_METHODS = FileCatalogClientBase.ADMIN_METHODS + [
@@ -147,7 +151,7 @@ class RucioFileCatalogClient(FileCatalogClientBase):
147
151
  self.authHost = options.get("AuthHost", None)
148
152
  self.caCertPath = Locations.getCAsLocation()
149
153
  try:
150
- sLog.info(f"Logging in with a proxy located at: {self.proxyPath}")
154
+ sLog.debug(f"Logging in with a proxy located at: {self.proxyPath}")
151
155
  sLog.debug("account: ", self.username)
152
156
  sLog.debug("rucio host: ", self.rucioHost)
153
157
  sLog.debug("auth host: ", self.authHost)
@@ -697,3 +701,193 @@ class RucioFileCatalogClient(FileCatalogClientBase):
697
701
  except Exception as err:
698
702
  return S_ERROR(str(err))
699
703
  return S_OK(resDict)
704
+
705
+ @checkCatalogArguments
706
+ def getFileUserMetadata(self, path):
707
+ """Get the meta data attached to a file, but also to
708
+ all its parents
709
+ """
710
+ path = next(iter(path))
711
+ resDict = {"Successful": {}, "Failed": {}}
712
+ try:
713
+ did = self.__getDidsFromLfn(path)
714
+ meta = next(self.client.get_metadata_bulk(dids=[did], inherit=True, plugin="ALL"))
715
+ if meta["did_type"] == "FILE": # Should we also return the metadata for the directories ?
716
+ resDict["Successful"][path] = meta
717
+ else:
718
+ resDict["Failed"][path] = "Not a file"
719
+ except DataIdentifierNotFound:
720
+ resDict["Failed"][path] = "No such file or directory"
721
+ except Exception as err:
722
+ return S_ERROR(str(err))
723
+ return S_OK(resDict)
724
+
725
+ @checkCatalogArguments
726
+ def getFileUserMetadataBulk(self, lfns):
727
+ """Get the meta data attached to a list of files, but also to
728
+ all their parents
729
+ """
730
+ resDict = {"Successful": {}, "Failed": {}}
731
+ dids = []
732
+ lfnChunks = breakListIntoChunks(lfns, 1000)
733
+ for lfnList in lfnChunks:
734
+ try:
735
+ dids = [self.__getDidsFromLfn(lfn) for lfn in lfnList]
736
+ except Exception as err:
737
+ return S_ERROR(str(err))
738
+ try:
739
+ for met in self.client.get_metadata_bulk(dids=dids, inherit=True):
740
+ lfn = met["name"]
741
+ resDict["Successful"][lfn] = met
742
+ for lfn in lfnList:
743
+ if lfn not in resDict["Successful"]:
744
+ resDict["Failed"][lfn] = "No such file or directory"
745
+ except Exception as err:
746
+ return S_ERROR(str(err))
747
+ return S_OK(resDict)
748
+
749
+ @checkCatalogArguments
750
+ def setMetadataBulk(self, pathMetadataDict):
751
+ """Add metadata for the given paths"""
752
+ resDict = {"Successful": {}, "Failed": {}}
753
+ dids = []
754
+ for path, metadataDict in pathMetadataDict.items():
755
+ try:
756
+ did = self.__getDidsFromLfn(path)
757
+ did["meta"] = metadataDict
758
+ dids.append(did)
759
+ except Exception as err:
760
+ return S_ERROR(str(err))
761
+ try:
762
+ self.client.set_dids_metadata_bulk(dids=dids, recursive=False)
763
+ except Exception as err:
764
+ return S_ERROR(str(err))
765
+ return S_OK(resDict)
766
+
767
+ @checkCatalogArguments
768
+ def setMetadata(self, path, metadataDict):
769
+ """Add metadata to the given path"""
770
+ pathMetadataDict = {}
771
+ path = next(iter(path))
772
+ pathMetadataDict[path] = metadataDict
773
+ return self.setMetadataBulk(pathMetadataDict)
774
+
775
+ @checkCatalogArguments
776
+ def removeMetadata(self, path, metadata):
777
+ """Remove the specified metadata for the given file"""
778
+ resDict = {"Successful": {}, "Failed": {}}
779
+ try:
780
+ did = self.__getDidsFromLfn(path)
781
+ failedMeta = {}
782
+ # TODO : Implement bulk delete_metadata method in Rucio
783
+ for meta in metadata:
784
+ try:
785
+ self.client.delete_metadata(scope=did["scope"], name=did["name"], key=meta)
786
+ except DataIdentifierNotFound:
787
+ return S_ERROR(f"File {path} not found")
788
+ except Exception as err:
789
+ failedMeta[meta] = str(err)
790
+
791
+ if failedMeta:
792
+ metaExample = list(failedMeta)[0]
793
+ result = S_ERROR(f"Failed to remove {len(failedMeta)} metadata, e.g. {failedMeta[metaExample]}")
794
+ result["FailedMetadata"] = failedMeta
795
+ except Exception as err:
796
+ return S_ERROR(str(err))
797
+ return S_OK()
798
+
799
+ def findFilesByMetadata(self, metadataFilterDict, path="/", timeout=120):
800
+ """find the dids for the given metadataFilterDict"""
801
+ ruciometadataFilterDict = self.__transform_DIRAC_filter_dict_to_Rucio_filter_dict([metadataFilterDict])
802
+ dids = []
803
+ for scope in self.scopes:
804
+ try:
805
+ dids.extend(self.client.list_dids(scope=scope, filters=ruciometadataFilterDict, did_type="all"))
806
+ except Exception as err:
807
+ return S_ERROR(str(err))
808
+ return S_OK(dids)
809
+
810
+ def __transform_DIRAC_operator_to_Rucio(self, DIRAC_dict):
811
+ """
812
+ Transforms a DIRAC's metadata Query dictionary to a Rucio-compatible dictionary.
813
+ This method takes a dictionary with DIRAC operators and converts it to a
814
+ dictionary with Rucio-compatible operators based on predefined mappings.
815
+ for example :
816
+ input_dict={'key1': 'value1', 'key2': {'>': 10}, 'key3': {'=': 10}}
817
+ return = {'key1': 'value1', 'key2.gt': 10, 'key3': 10}
818
+ """
819
+ rucio_dict = {}
820
+ operator_mapping = {">": ".gt", "<": ".lt", ">=": ".gte", "<=": ".lte", "=<": ".lte", "!=": ".ne", "=": ""}
821
+
822
+ for key, value in DIRAC_dict.items():
823
+ if isinstance(value, dict):
824
+ for operator, num in value.items():
825
+ if operator in operator_mapping:
826
+ mapped_operator = operator_mapping[operator]
827
+ rucio_dict[f"{key}{mapped_operator}"] = num
828
+ else:
829
+ rucio_dict[key] = value
830
+
831
+ return rucio_dict
832
+
833
+ def __transform_dict_with_in_operateur(self, DIRAC_dict_with_in_operator_list):
834
+ """
835
+ Transforms a list of DIRAC dictionaries containing 'in' operators into a combined list of dictionaries,
836
+ expanding the 'in' operator into individual dictionaries while preserving other keys.
837
+ example
838
+ input_dict_list = [{'particle': {'in': ['proton','electron']},'site': {'in': [ "LaPalma", 'paranal']},'configuration_id': {'=': 14} } ]
839
+ return = [{'particle': 'proton', 'site': 'LaPalma', 'configuration_id': {'=': 14} }, {'particle': 'proton', 'site': 'paranal', 'configuration_id': {'=': 14} }, {'particle': 'electron', 'site': 'LaPalma', 'configuration_id': {'=': 14} }, {'particle': 'electron', 'site': 'paranal', 'configuration_id': {'=': 14} }]
840
+ """
841
+ if not isinstance(DIRAC_dict_with_in_operator_list, list):
842
+ raise TypeError("DIRAC_dict_with_in_operator_list must be a list of dictionaries")
843
+
844
+ combined_dict_list = [] # Final list of transformed dictionaries
845
+ break_reached = False # Boolean to track if 'in' was found and processed in any dictionary
846
+
847
+ # Process each dictionary in the input list
848
+ for DIRAC_dict_with_in_operator in DIRAC_dict_with_in_operator_list:
849
+ if not isinstance(DIRAC_dict_with_in_operator, dict):
850
+ raise TypeError("Each element in DIRAC_dict_with_in_operator_list must be a dictionary")
851
+
852
+ in_key = None
853
+ in_values = []
854
+
855
+ # Extract the key with 'in' operator and the list of values
856
+ for key, value in DIRAC_dict_with_in_operator.items():
857
+ if isinstance(value, dict) and "in" in value:
858
+ in_key = key
859
+ in_values = value["in"]
860
+ break_reached = True # 'in' operator found
861
+ break
862
+
863
+ # If an 'in' key exists, expand the dictionary for each value
864
+ if in_key:
865
+ for val in in_values:
866
+ # Copy the original dictionary and replace the 'in' key
867
+ new_dict = DIRAC_dict_with_in_operator.copy()
868
+ new_dict[in_key] = val # Replace the 'in' key with the current value
869
+ combined_dict_list.append(new_dict)
870
+ else:
871
+ # If no 'in' key, simply add the input dictionary as-is
872
+ combined_dict_list.append(DIRAC_dict_with_in_operator)
873
+
874
+ return combined_dict_list, break_reached
875
+
876
+ def __transform_DIRAC_filter_dict_to_Rucio_filter_dict(self, DIRAC_filter_dict_list):
877
+ """
878
+ Transforms a list of DIRAC filter dictionaries into a list of Rucio filter dictionaries.
879
+ This method takes a list of filter dictionaries used in DIRAC and converts them into a format
880
+ that is compatible with Rucio. It handles the transformation of operators and expands filters
881
+ that use the 'in' operator.
882
+ example:
883
+ input_dict_list = [{'particle': {'in': ['proton','electron']},'site': {'in': [ "LaPalma", 'paranal']},'configuration_id': {'=': 14} } ]
884
+ return = [{'particle': 'proton', 'site': 'LaPalma', 'configuration_id': 14}, {'particle': 'proton', 'site': 'paranal', 'configuration_id': 14}, {'particle': 'electron', 'site': 'LaPalma', 'configuration_id': 14}, {'particle': 'electron', 'site': 'paranal', 'configuration_id': 14}]
885
+ """
886
+ break_detected = True
887
+ DIRAC_expanded_filters = DIRAC_filter_dict_list
888
+ while break_detected:
889
+ DIRAC_expanded_filters, break_detected = self.__transform_dict_with_in_operateur(DIRAC_expanded_filters)
890
+ Rucio_filters = []
891
+ for filter in DIRAC_expanded_filters:
892
+ Rucio_filters.append(self.__transform_DIRAC_operator_to_Rucio(filter))
893
+ return Rucio_filters
@@ -0,0 +1,181 @@
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch
3
+ from DIRAC.Resources.Catalog.RucioFileCatalogClient import RucioFileCatalogClient
4
+
5
+
6
+ class TestRucioFileCatalogClient(unittest.TestCase):
7
+ def setUp(self):
8
+ self.patcher = patch.object(RucioFileCatalogClient, "client", new_callable=MagicMock)
9
+ self.client = RucioFileCatalogClient()
10
+ self.client.scopes = ["test_scope"]
11
+ self.patcher.start()
12
+
13
+ def tearDown(self):
14
+ self.patcher.stop()
15
+
16
+ def test_transform_DIRAC_operator_to_Rucio(self):
17
+ DIRAC_dict = {"key1": "value1", "key2": {">": 10}, "key3": {"=": 10}}
18
+ expected_output = {"key1": "value1", "key2.gt": 10, "key3": 10}
19
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_operator_to_Rucio(DIRAC_dict)
20
+ self.assertEqual(result, expected_output)
21
+
22
+ def test_transform_dict_with_in_operateur_2steps(self):
23
+ DIRAC_dict_with_in_operator_list = [
24
+ {
25
+ "particle": {"in": ["proton", "electron"]},
26
+ "site": {"in": ["LaPalma", "paranal"]},
27
+ "configuration_id": {"=": 14},
28
+ }
29
+ ]
30
+ expected_intermediate_output = [
31
+ {"particle": "proton", "site": {"in": ["LaPalma", "paranal"]}, "configuration_id": {"=": 14}},
32
+ {"particle": "electron", "site": {"in": ["LaPalma", "paranal"]}, "configuration_id": {"=": 14}},
33
+ ]
34
+ expected_final_output = [
35
+ {"particle": "proton", "site": "LaPalma", "configuration_id": {"=": 14}},
36
+ {"particle": "proton", "site": "paranal", "configuration_id": {"=": 14}},
37
+ {"particle": "electron", "site": "LaPalma", "configuration_id": {"=": 14}},
38
+ {"particle": "electron", "site": "paranal", "configuration_id": {"=": 14}},
39
+ ]
40
+ result_intermediate, _ = self.client._RucioFileCatalogClient__transform_dict_with_in_operateur(
41
+ DIRAC_dict_with_in_operator_list
42
+ )
43
+ self.assertEqual(result_intermediate, expected_intermediate_output)
44
+ result_final, _ = self.client._RucioFileCatalogClient__transform_dict_with_in_operateur(result_intermediate)
45
+ self.assertEqual(result_final, expected_final_output)
46
+
47
+ def test_transform_DIRAC_operator_to_Rucio_simple_key_value(self):
48
+ input_dict = {"key1": "value1", "key2": "value2"}
49
+ expected_output = {"key1": "value1", "key2": "value2"}
50
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_operator_to_Rucio(input_dict)
51
+ self.assertEqual(result, expected_output)
52
+
53
+ def test_transform_DIRAC_operator_to_Rucio_nested_dict_with_operators_gl(self):
54
+ input_dict = {
55
+ "start": {">=": 10},
56
+ "end": {">": 5},
57
+ "pointingZ": {">=": 0.1},
58
+ "organization": "ViaCorp",
59
+ "data_levels": "DL3",
60
+ }
61
+ expected_output = {
62
+ "start.gte": 10,
63
+ "end.gt": 5,
64
+ "pointingZ.gte": 0.1,
65
+ "organization": "ViaCorp",
66
+ "data_levels": "DL3",
67
+ }
68
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_operator_to_Rucio(input_dict)
69
+ self.assertEqual(result, expected_output)
70
+
71
+ def test_transform_DIRAC_operator_to_Rucio_nested_dict_with_operators_equals(self):
72
+ input_dict = {"start": {"=": 10}, "pointingZ": {"=": 0.1}, "organization": "ViaCorp", "data_levels": "DL3"}
73
+ expected_output = {"start": 10, "pointingZ": 0.1, "organization": "ViaCorp", "data_levels": "DL3"}
74
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_operator_to_Rucio(input_dict)
75
+ assert result == expected_output
76
+
77
+ def test_transform_DIRAC_operator_to_Rucio_mixed_dict(self):
78
+ input_dict = {"key1": "value1", "key2": {">": 10}, "key3": {"=": 10}}
79
+ expected_output = {"key1": "value1", "key2.gt": 10, "key3": 10}
80
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_operator_to_Rucio(input_dict)
81
+ assert result == expected_output
82
+
83
+ def test_transform_DIRAC_operator_to_Rucio_in_operator(self):
84
+ input_dict = [
85
+ {
86
+ "analysis_prog": {"in": ["ctapipe-merge", "ctapipe-process", "ctapipe-apply-models"]},
87
+ "key1": "value1",
88
+ "key3": {"=": 10},
89
+ "key4": {"<": 5},
90
+ }
91
+ ]
92
+ expected_intermediate = [
93
+ {"key1": "value1", "key3": 10, "key4.lt": 5, "analysis_prog": "ctapipe-merge"},
94
+ {"key1": "value1", "key3": 10, "key4.lt": 5, "analysis_prog": "ctapipe-process"},
95
+ {"key1": "value1", "key3": 10, "key4.lt": 5, "analysis_prog": "ctapipe-apply-models"},
96
+ ]
97
+ result_interm = self.client._RucioFileCatalogClient__transform_DIRAC_filter_dict_to_Rucio_filter_dict(
98
+ input_dict
99
+ )
100
+ assert result_interm == expected_intermediate
101
+
102
+ def test_transform_DIRAC_operator_to_Rucio_2timesin_operator(self):
103
+ input_dict = [{"particle": {"in": ["proton", "electron"]}, "site": {"in": ["LaPalma", "paranal"]}}]
104
+ expected = [
105
+ {"particle": "proton", "site": "LaPalma"},
106
+ {"particle": "proton", "site": "paranal"},
107
+ {"particle": "electron", "site": "LaPalma"},
108
+ {"particle": "electron", "site": "paranal"},
109
+ ]
110
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_filter_dict_to_Rucio_filter_dict(input_dict)
111
+ assert result == expected
112
+
113
+ def test_2timesin_mix_operator(self):
114
+ input_dict = [
115
+ {
116
+ "particle": {"in": ["proton", "electron"]},
117
+ "site": {"in": ["LaPalma", "paranal"]},
118
+ "configuration_id": {"=": 14},
119
+ }
120
+ ]
121
+ expected = [
122
+ {"particle": "proton", "site": "LaPalma", "configuration_id": 14},
123
+ {"particle": "proton", "site": "paranal", "configuration_id": 14},
124
+ {"particle": "electron", "site": "LaPalma", "configuration_id": 14},
125
+ {"particle": "electron", "site": "paranal", "configuration_id": 14},
126
+ ]
127
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_filter_dict_to_Rucio_filter_dict(input_dict)
128
+ assert result == expected
129
+
130
+ input_dict = [
131
+ {
132
+ "particle": {"in": ["proton", "electron"]},
133
+ "configuration_id": {"=": 14},
134
+ "site": {"in": ["LaPalma", "paranal"]},
135
+ }
136
+ ]
137
+ expected = [
138
+ {"particle": "proton", "configuration_id": 14, "site": "LaPalma"},
139
+ {"particle": "proton", "configuration_id": 14, "site": "paranal"},
140
+ {"particle": "electron", "configuration_id": 14, "site": "LaPalma"},
141
+ {"particle": "electron", "configuration_id": 14, "site": "paranal"},
142
+ ]
143
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_filter_dict_to_Rucio_filter_dict(input_dict)
144
+ assert result == expected
145
+
146
+ def test_transform_DIRAC_filter_dict_to_Rucio_filter_dict(self):
147
+ DIRAC_filter_dict_list = [
148
+ {
149
+ "particle": {"in": ["proton", "electron"]},
150
+ "configuration_id": {"=": 14},
151
+ "site": {"in": ["LaPalma", "paranal"]},
152
+ }
153
+ ]
154
+ expected_output = [
155
+ {"particle": "proton", "configuration_id": 14, "site": "LaPalma"},
156
+ {"particle": "proton", "configuration_id": 14, "site": "paranal"},
157
+ {"particle": "electron", "configuration_id": 14, "site": "LaPalma"},
158
+ {"particle": "electron", "configuration_id": 14, "site": "paranal"},
159
+ ]
160
+ result = self.client._RucioFileCatalogClient__transform_DIRAC_filter_dict_to_Rucio_filter_dict(
161
+ DIRAC_filter_dict_list
162
+ )
163
+ self.assertEqual(result, expected_output)
164
+
165
+ def test_findFilesByMetadata(self):
166
+ self.client.client.list_dids.return_value = ["did1", "did2"]
167
+ metadataFilterDict = {"key1": "value1"}
168
+ result = self.client.findFilesByMetadata(metadataFilterDict)
169
+ self.assertTrue(result["OK"])
170
+ self.assertEqual(result["Value"], ["did1", "did2"])
171
+
172
+ def test_findFilesByMetadata_with_error(self):
173
+ self.client.client.list_dids.side_effect = Exception("Test error")
174
+ metadataFilterDict = {"key1": "value1"}
175
+ result = self.client.findFilesByMetadata(metadataFilterDict)
176
+ self.assertFalse(result["OK"])
177
+ self.assertIn("Test error", result["Message"])
178
+
179
+
180
+ if __name__ == "__main__":
181
+ unittest.main()
@@ -1,4 +1,4 @@
1
- """ AREX Computing Element (ARC REST interface)
1
+ """AREX Computing Element (ARC REST interface)
2
2
 
3
3
  Allows interacting with ARC AREX services via a REST interface.
4
4
 
@@ -48,6 +48,7 @@ import json
48
48
  import os
49
49
  import shutil
50
50
  import stat
51
+ import tempfile
51
52
  import uuid
52
53
 
53
54
  import requests
@@ -57,7 +58,6 @@ from DIRAC.Core.Security import Locations
57
58
  from DIRAC.Core.Security.ProxyInfo import getVOfromProxyGroup
58
59
  from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error
59
60
  from DIRAC.Resources.Computing.ComputingElement import ComputingElement
60
- from DIRAC.Resources.Computing.PilotBundle import writeScript
61
61
  from DIRAC.WorkloadManagementSystem.Client import PilotStatus
62
62
 
63
63
  MANDATORY_PARAMETERS = ["Queue"]
@@ -258,9 +258,6 @@ class AREXComputingElement(ComputingElement):
258
258
  if not (self.token or self.proxy):
259
259
  self.log.error("Proxy or token not set")
260
260
  return S_ERROR("Proxy or token not set")
261
- if not self.proxy and self.alwaysIncludeProxy:
262
- self.log.error("Proxy required but not set")
263
- return S_ERROR("Proxy required but not set")
264
261
 
265
262
  # If a token is set, we use it
266
263
  if self.token:
@@ -498,7 +495,11 @@ class AREXComputingElement(ComputingElement):
498
495
  if not os.access(executableFile, os.X_OK):
499
496
  os.chmod(executableFile, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH + stat.S_IXOTH)
500
497
 
501
- return writeScript(wrapperContent, os.getcwd())
498
+ with tempfile.NamedTemporaryFile(
499
+ "w", delete=False, prefix="preamble-", suffix=f"-{executableFile}"
500
+ ) as bundleFile:
501
+ bundleFile.write(wrapperContent)
502
+ return bundleFile.name
502
503
 
503
504
  def _getArcJobID(self, executableFile, inputs, outputs, delegation):
504
505
  """Get an ARC JobID endpoint to upload executables and inputs.
@@ -806,7 +807,23 @@ class AREXComputingElement(ComputingElement):
806
807
  return S_ERROR(f"Failed decoding the status of the CE")
807
808
 
808
809
  # Look only in the relevant section out of the headache
809
- queueInfo = ceData["Domains"]["AdminDomain"]["Services"]["ComputingService"]["ComputingShare"]
810
+ # This "safe_get" function allows to go down the dictionary
811
+ # even if some elements are lists instead of dictionaries
812
+ # and returns None if any element is not found
813
+ # FIXME: this is a temporary measure to be removed after https://github.com/DIRACGrid/DIRAC/issues/8354
814
+ def safe_get(d, *keys):
815
+ for k in keys:
816
+ if isinstance(d, list):
817
+ d = d[0] # assume first element
818
+ d = d.get(k) if isinstance(d, dict) else None
819
+ if d is None:
820
+ break
821
+ return d
822
+
823
+ queueInfo = safe_get(ceData, "Domains", "AdminDomain", "Services", "ComputingService", "ComputingShare")
824
+ if queueInfo is None:
825
+ self.log.error("Failed to extract queue info")
826
+
810
827
  if not isinstance(queueInfo, list):
811
828
  queueInfo = [queueInfo]
812
829
 
@@ -818,7 +835,7 @@ class AREXComputingElement(ComputingElement):
818
835
  for qi in queueInfo:
819
836
  if qi["ID"].endswith(magic):
820
837
  result["RunningJobs"] = int(qi["RunningJobs"])
821
- result["WaitingJobs"] = int(qi["WaitingJobs"])
838
+ result["WaitingJobs"] = int(qi["WaitingJobs"]) + int(qi["StagingJobs"]) + int(qi["PreLRMSWaitingJobs"])
822
839
  break # Pick the first (should be only ...) matching queue + VO
823
840
  else:
824
841
  return S_ERROR(f"Could not find the queue {self.queue} associated to VO {vo}")