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
@@ -1,9 +1,11 @@
1
- """ FTS3Job module containing only the FTS3Job class """
1
+ """FTS3Job module containing only the FTS3Job class"""
2
2
 
3
3
  import datetime
4
4
  import errno
5
+ import os
5
6
  from packaging.version import Version
6
7
 
8
+ from cachetools import cachedmethod, LRUCache
7
9
 
8
10
  # Requires at least version 3.3.3
9
11
  from fts3 import __version__ as fts3_version
@@ -26,8 +28,9 @@ from DIRAC.Resources.Storage.StorageElement import StorageElement
26
28
 
27
29
  from DIRAC.FrameworkSystem.Client.Logger import gLogger
28
30
  from DIRAC.FrameworkSystem.Client.TokenManagerClient import gTokenManager
31
+ from DIRAC.FrameworkSystem.Utilities.TokenManagementUtilities import getIdProviderClient
29
32
 
30
- from DIRAC.Core.Utilities.ReturnValues import S_OK, S_ERROR
33
+ from DIRAC.Core.Utilities.ReturnValues import S_OK, S_ERROR, returnValueOrRaise
31
34
  from DIRAC.Core.Utilities.DErrno import cmpError
32
35
 
33
36
  from DIRAC.Core.Utilities.JEncode import JSerializable
@@ -36,6 +39,10 @@ from DIRAC.DataManagementSystem.Client.FTS3File import FTS3File
36
39
  # 3 days in seconds
37
40
  BRING_ONLINE_TIMEOUT = 259200
38
41
 
42
+ # Number of IdP to keep in cache. Should correspond roughly
43
+ # to the number of groups performing transfers
44
+ IDP_CACHE_SIZE = 8
45
+
39
46
 
40
47
  class FTS3Job(JSerializable):
41
48
  """Abstract class to represent a job to be executed by FTS. It belongs
@@ -78,6 +85,8 @@ class FTS3Job(JSerializable):
78
85
  "userGroup",
79
86
  ]
80
87
 
88
+ _idp_cache = LRUCache(maxsize=IDP_CACHE_SIZE)
89
+
81
90
  def __init__(self):
82
91
  self.submitTime = None
83
92
  self.lastUpdate = None
@@ -111,7 +120,12 @@ class FTS3Job(JSerializable):
111
120
  # temporary used only for accounting
112
121
  # it is set by the monitor method
113
122
  # when a job is in a final state
114
- self.accountingDict = None
123
+ self.accountingDicts = None
124
+
125
+ @classmethod
126
+ @cachedmethod(lambda cls: cls._idp_cache)
127
+ def _getIdpClient(cls, group_name: str):
128
+ return returnValueOrRaise(getIdProviderClient(group_name, None, client_name_prefix="fts"))
115
129
 
116
130
  def monitor(self, context=None, ftsServer=None, ucert=None):
117
131
  """Queries the fts server to monitor the job.
@@ -143,7 +157,6 @@ class FTS3Job(JSerializable):
143
157
 
144
158
  if not self.ftsGUID:
145
159
  return S_ERROR("FTSGUID not set, FTS job not submitted?")
146
-
147
160
  if not context:
148
161
  if not ftsServer:
149
162
  ftsServer = self.ftsServer
@@ -170,13 +183,14 @@ class FTS3Job(JSerializable):
170
183
  self.error = jobStatusDict["reason"]
171
184
 
172
185
  if newStatus in self.FINAL_STATES:
173
- self._fillAccountingDict(jobStatusDict)
186
+ self._fillAccountingDicts(jobStatusDict)
174
187
 
175
188
  filesInfoList = jobStatusDict["files"]
176
189
  filesStatus = {}
177
190
  statusSummary = {}
178
191
 
179
192
  # Make a copy, since we are potentially
193
+
180
194
  # deleting objects
181
195
  for fileDict in list(filesInfoList):
182
196
  file_state = fileDict["file_state"].capitalize()
@@ -231,7 +245,7 @@ class FTS3Job(JSerializable):
231
245
  # so we put this back into the monitoring data such that the accounting is done properly
232
246
  jobStatusDict["files"] = filesInfoList
233
247
  if newStatus in self.FINAL_STATES:
234
- self._fillAccountingDict(jobStatusDict)
248
+ self._fillAccountingDicts(jobStatusDict)
235
249
 
236
250
  total = len(filesInfoList)
237
251
  completed = sum(statusSummary.get(state, 0) for state in FTS3File.FTS_FINAL_STATES)
@@ -509,11 +523,10 @@ class FTS3Job(JSerializable):
509
523
  if not res["OK"]:
510
524
  return res
511
525
  srcTokenPath = res["Value"]
512
- res = gTokenManager.getToken(
513
- userGroup=self.userGroup,
514
- requiredTimeLeft=3600,
526
+ res = self._getIdpClient(self.userGroup).fetchToken(
527
+ grant_type="client_credentials",
515
528
  scope=[f"storage.read:/{srcTokenPath}", "offline_access"],
516
- useCache=False,
529
+ # TODO: add a specific audience
517
530
  )
518
531
  if not res["OK"]:
519
532
  return res
@@ -528,11 +541,17 @@ class FTS3Job(JSerializable):
528
541
  if not res["OK"]:
529
542
  return res
530
543
  dstTokenPath = res["Value"]
531
- res = gTokenManager.getToken(
532
- userGroup=self.userGroup,
533
- requiredTimeLeft=3600,
534
- scope=[f"storage.modify:/{dstTokenPath}", f"storage.read:/{dstTokenPath}", "offline_access"],
535
- useCache=False,
544
+ res = self._getIdpClient(self.userGroup).fetchToken(
545
+ grant_type="client_credentials",
546
+ scope=[
547
+ f"storage.modify:/{dstTokenPath}",
548
+ f"storage.read:/{dstTokenPath}",
549
+ # Needed because CNAF
550
+ # https://ggus.eu/index.php?mode=ticket_info&ticket_id=165048
551
+ f"storage.read:/{os.path.dirname(dstTokenPath)}",
552
+ "offline_access",
553
+ ],
554
+ # TODO: add a specific audience
536
555
  )
537
556
  if not res["OK"]:
538
557
  return res
@@ -728,6 +747,7 @@ class FTS3Job(JSerializable):
728
747
  retry=3,
729
748
  metadata=job_metadata,
730
749
  priority=self.priority,
750
+ unmanaged_tokens=True,
731
751
  **dest_spacetoken,
732
752
  )
733
753
 
@@ -882,9 +902,9 @@ class FTS3Job(JSerializable):
882
902
  gLogger.exception("Error generating context", repr(e))
883
903
  return S_ERROR(repr(e))
884
904
 
885
- def _fillAccountingDict(self, jobStatusDict):
886
- """This methods generates the necessary information to create a DataOperation
887
- accounting record, and stores them as a instance attribute.
905
+ def _fillAccountingDicts(self, jobStatusDict):
906
+ """This methods generates the necessary information to create DataOperation
907
+ accounting records, and stores them as a instance attribute.
888
908
 
889
909
  For it to be relevant, it should be called only when the job is in a final state.
890
910
 
@@ -893,6 +913,7 @@ class FTS3Job(JSerializable):
893
913
  :returns: None
894
914
  """
895
915
 
916
+ accountingDicts = []
896
917
  accountingDict = dict()
897
918
  sourceSE = None
898
919
  targetSE = None
@@ -903,16 +924,24 @@ class FTS3Job(JSerializable):
903
924
  accountingDict["Protocol"] = "FTS3"
904
925
  accountingDict["ExecutionSite"] = self.ftsServer
905
926
 
927
+ # Registration values must be set anyway
928
+ accountingDict["RegistrationTime"] = 0.0
929
+ accountingDict["RegistrationOK"] = 0
930
+ accountingDict["RegistrationTotal"] = 0
931
+
906
932
  # We cannot rely on all the transient attributes (like self.filesToSubmit)
907
933
  # because it is probably not filed by the time we monitor !
908
934
 
909
935
  filesInfoList = jobStatusDict["files"]
910
936
  successfulFiles = []
937
+ failedFiles = []
911
938
 
912
939
  for fileDict in filesInfoList:
913
940
  file_state = fileDict["file_state"].capitalize()
914
941
  if file_state in FTS3File.FTS_SUCCESS_STATES:
915
942
  successfulFiles.append(fileDict)
943
+ else:
944
+ failedFiles.append(fileDict)
916
945
 
917
946
  job_metadata = jobStatusDict["job_metadata"]
918
947
  # previous version of the code did not have dictionary as
@@ -921,23 +950,31 @@ class FTS3Job(JSerializable):
921
950
  sourceSE = job_metadata.get("sourceSE")
922
951
  targetSE = job_metadata.get("targetSE")
923
952
 
924
- accountingDict["TransferOK"] = len(successfulFiles)
925
- accountingDict["TransferTotal"] = len(filesInfoList)
926
- # We need this if in the list comprehension because staging only jobs have `None` as filesize
927
- accountingDict["TransferSize"] = sum(
928
- fileDict["filesize"] for fileDict in successfulFiles if fileDict["filesize"]
929
- )
930
- accountingDict["FinalStatus"] = self.status
931
953
  accountingDict["Source"] = sourceSE
932
954
  accountingDict["Destination"] = targetSE
933
- # We need this if in the list comprehension because staging only jobs have `None` as tx_duration
934
- accountingDict["TransferTime"] = sum(
935
- int(fileDict["tx_duration"]) for fileDict in successfulFiles if fileDict["tx_duration"]
936
- )
937
955
 
938
- # Registration values must be set anyway
939
- accountingDict["RegistrationTime"] = 0.0
940
- accountingDict["RegistrationOK"] = 0
941
- accountingDict["RegistrationTotal"] = 0
956
+ if successfulFiles:
957
+ successfulDict = accountingDict.copy()
958
+ successfulDict["TransferOK"] = len(successfulFiles)
959
+ successfulDict["TransferTotal"] = len(successfulFiles)
960
+ # We need this if in the list comprehension because staging only jobs have `None` as filesize
961
+ successfulDict["TransferSize"] = sum(
962
+ fileDict["filesize"] for fileDict in successfulFiles if fileDict["filesize"]
963
+ )
964
+ successfulDict["FinalStatus"] = "Finished"
942
965
 
943
- self.accountingDict = accountingDict
966
+ # We need this if in the list comprehension because staging only jobs have `None` as tx_duration
967
+ successfulDict["TransferTime"] = sum(
968
+ int(fileDict["tx_duration"]) for fileDict in successfulFiles if fileDict["tx_duration"]
969
+ )
970
+ accountingDicts.append(successfulDict)
971
+ if failedFiles:
972
+ failedDict = accountingDict.copy()
973
+ failedDict["TransferOK"] = 0
974
+ failedDict["TransferTotal"] = len(failedFiles)
975
+ failedDict["TransferSize"] = 0
976
+ failedDict["FinalStatus"] = "Failed"
977
+ failedDict["TransferTime"] = 0
978
+ accountingDicts.append(failedDict)
979
+
980
+ self.accountingDicts = accountingDicts
@@ -16,6 +16,7 @@ from sqlalchemy import (
16
16
  Enum,
17
17
  Float,
18
18
  ForeignKey,
19
+ Index,
19
20
  Integer,
20
21
  MetaData,
21
22
  SmallInteger,
@@ -85,6 +86,7 @@ fts3JobTable = Table(
85
86
  Column("error", String(2048)),
86
87
  Column("status", Enum(*FTS3Job.ALL_STATES), server_default=FTS3Job.INIT_STATE, index=True),
87
88
  Column("assignment", String(255), server_default=None),
89
+ Index("idx_jobs_lastupdate_assignment", "lastUpdate", "assignment"),
88
90
  mysql_engine="InnoDB",
89
91
  )
90
92
 
@@ -110,6 +112,7 @@ fts3OperationTable = Table(
110
112
  Column("error", String(1024)),
111
113
  Column("type", String(255)),
112
114
  Column("assignment", String(255), server_default=None),
115
+ Index("idx_operations_lastupdate_assignment", "lastUpdate", "assignment"),
113
116
  mysql_engine="InnoDB",
114
117
  )
115
118
 
@@ -12,7 +12,7 @@ class DatasetManager:
12
12
  _tables["FC_MetaDatasets"] = {
13
13
  "Fields": {
14
14
  "DatasetID": "INT AUTO_INCREMENT",
15
- "DatasetName": "VARCHAR(128) CHARACTER SET utfmb4 COLLATE utf8mb4_bin NOT NULL",
15
+ "DatasetName": "VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL",
16
16
  "MetaQuery": "VARCHAR(512)",
17
17
  "DirID": "INT NOT NULL DEFAULT 0",
18
18
  "TotalSize": "BIGINT UNSIGNED NOT NULL",
@@ -1,7 +1,8 @@
1
1
  """
2
- This module contains helper methods for accessing operational attributes or parameters of DMS objects
2
+ This module contains helper methods for accessing operational attributes or parameters of DMS objects
3
3
 
4
4
  """
5
+
5
6
  from collections import defaultdict
6
7
  from DIRAC import gConfig, gLogger, S_OK, S_ERROR
7
8
  from DIRAC.ConfigurationSystem.Client.Helpers.Path import cfgPath
@@ -17,6 +18,9 @@ sLog = gLogger.getSubLogger(__name__)
17
18
  def resolveSEGroup(seGroupList, allSEs=None):
18
19
  """
19
20
  Resolves recursively a (list of) SEs that can be groupSEs
21
+ For modules, JobWrapper or whatever runs at a given site,
22
+ prefer using :py:func:`~DIRAC.DataManagementSystem.Utilities.ResolveSE.getDestinationSEList`
23
+
20
24
 
21
25
  :param seGroupList: list of SEs to resolve or comma-separated SEs
22
26
  :type seGroupList: list or string
@@ -402,7 +406,7 @@ class DMSHelpers:
402
406
  return sesAtSite
403
407
  foundSEs = set(seList) & set(sesAtSite["Value"])
404
408
  if not foundSEs:
405
- sLog.warn("No SE found at that site", f"in group {seGroup} at {site}")
409
+ sLog.verbose("No SE found at that site", f"in group {seGroup} at {site}")
406
410
  return S_OK()
407
411
  return S_OK(sorted(foundSEs))
408
412
 
@@ -41,6 +41,7 @@ class CreateMovingRequest:
41
41
  self.flags = [
42
42
  ("C", "CheckMigration", "Ensure the LFNs are migrated to tape before removing any replicas"),
43
43
  ("X", "Execute", "Put Requests, else dryrun"),
44
+ ("", "SourceOnly", "Only treat files that are already at the Source-SE"),
44
45
  ]
45
46
  self.registerSwitchesAndParseCommandLine()
46
47
  self.getLFNList()
@@ -208,6 +209,7 @@ class CreateMovingRequest:
208
209
 
209
210
  replicate = Operation()
210
211
  replicate.Type = "ReplicateAndRegister"
212
+ replicate.SourceSE = ",".join(self.switches.get("SourceSE", []))
211
213
  replicate.TargetSE = self.switches.get("TargetSE")
212
214
  self.addLFNs(replicate, lfnChunk, addPFN=True)
213
215
  request.addOperation(replicate)
@@ -177,7 +177,6 @@ def main():
177
177
  for src, dst in ((x, y) for x in fromSE for y in targetSE):
178
178
  if ftsTab:
179
179
  try:
180
- # breakpoint()
181
180
  fts3TpcProto = fts3Plugin.selectTPCProtocols(sourceSEName=ses[src].name, destSEName=ses[dst].name)
182
181
  res = ses[dst].generateTransferURLsBetweenSEs(lfn, ses[src], fts3TpcProto)
183
182
  except ValueError as e:
@@ -60,7 +60,6 @@ from collections import defaultdict
60
60
 
61
61
  import importlib_metadata as metadata
62
62
  import importlib_resources
63
- import MySQLdb
64
63
  from diraccfg import CFG
65
64
  from prompt_toolkit import prompt
66
65
 
@@ -96,7 +95,6 @@ from DIRAC.Core.Utilities.Extensions import (
96
95
  findServices,
97
96
  )
98
97
  from DIRAC.Core.Utilities.File import mkDir, mkLink
99
- from DIRAC.Core.Utilities.MySQL import MySQL
100
98
  from DIRAC.Core.Utilities.PrettyPrint import printTable
101
99
  from DIRAC.Core.Utilities.ReturnValues import S_ERROR, S_OK
102
100
  from DIRAC.Core.Utilities.Subprocess import systemCall
@@ -2055,6 +2053,8 @@ class ComponentInstaller:
2055
2053
  """
2056
2054
  Install requested DB in MySQL server
2057
2055
  """
2056
+ import MySQLdb
2057
+
2058
2058
  dbName = MySQLdb.escape_string(dbName.encode()).decode()
2059
2059
  if not self.mysqlRootPwd:
2060
2060
  rootPwdPath = cfgInstallPath("Database", "RootPwd")
@@ -2202,6 +2202,8 @@ class ComponentInstaller:
2202
2202
  """
2203
2203
  Execute MySQL Command
2204
2204
  """
2205
+ from DIRAC.Core.Utilities.MySQL import MySQL
2206
+
2205
2207
  if not self.mysqlRootPwd:
2206
2208
  return S_ERROR("MySQL root password is not defined")
2207
2209
  if dbName not in self.db:
@@ -11,7 +11,7 @@
11
11
  import textwrap
12
12
  from threading import Lock
13
13
 
14
- from cachetools import TTLCache, cached
14
+ from cachetools import TTLCache, cachedmethod
15
15
 
16
16
  from DIRAC import S_ERROR, S_OK, gLogger
17
17
  from DIRAC.ConfigurationSystem.Client.Helpers import Registry
@@ -25,6 +25,10 @@ from DIRAC.Resources.ProxyProvider.ProxyProviderFactory import ProxyProviderFact
25
25
 
26
26
  DEFAULT_MAIL_FROM = "proxymanager@diracgrid.org"
27
27
 
28
+ # Module-level cache for getProxyStrength method (shared across ProxyDB instances)
29
+ _get_proxy_strength_cache = TTLCache(maxsize=1000, ttl=600)
30
+ _get_proxy_strength_lock = Lock()
31
+
28
32
 
29
33
  class ProxyDB(DB):
30
34
  NOTIFICATION_TIMES = [2592000, 1296000]
@@ -398,7 +402,7 @@ class ProxyDB(DB):
398
402
  return S_ERROR(", ".join(errMsgs))
399
403
  return result
400
404
 
401
- @cached(TTLCache(maxsize=1000, ttl=600), lock=Lock())
405
+ @cachedmethod(lambda self: _get_proxy_strength_cache, lock=lambda self: _get_proxy_strength_lock)
402
406
  def getProxyStrength(self, userDN, userGroup=None, vomsAttr=None):
403
407
  """Load the proxy in cache corresponding to the criteria, and check its strength
404
408
 
@@ -601,13 +605,13 @@ class ProxyDB(DB):
601
605
  :return: S_OK(dict)/S_ERROR() -- dict contain attribute and VOMS VO
602
606
  """
603
607
  if requiredVOMSAttribute:
604
- return S_OK({"attribute": requiredVOMSAttribute, "VOMSVO": Registry.getVOMSVOForGroup(userGroup)})
608
+ return S_OK({"attribute": requiredVOMSAttribute, "VO": Registry.getVOForGroup(userGroup)})
605
609
 
606
610
  csVOMSMapping = Registry.getVOMSAttributeForGroup(userGroup)
607
611
  if not csVOMSMapping:
608
612
  return S_ERROR(f"No mapping defined for group {userGroup} in the CS")
609
613
 
610
- return S_OK({"attribute": csVOMSMapping, "VOMSVO": Registry.getVOMSVOForGroup(userGroup)})
614
+ return S_OK({"attribute": csVOMSMapping, "VO": Registry.getVOForGroup(userGroup)})
611
615
 
612
616
  def getVOMSProxy(self, userDN, userGroup, requiredLifeTime=None, requestedVOMSAttr=None):
613
617
  """Get proxy string from the Proxy Repository for use with userDN
@@ -624,7 +628,7 @@ class ProxyDB(DB):
624
628
  if not retVal["OK"]:
625
629
  return retVal
626
630
  vomsAttr = retVal["Value"]["attribute"]
627
- vomsVO = retVal["Value"]["VOMSVO"]
631
+ vomsVO = retVal["Value"]["VO"]
628
632
 
629
633
  # Look in the cache
630
634
  retVal = self.__getPemAndTimeLeft(userDN, userGroup, vomsAttr)
@@ -10,11 +10,12 @@ DEFAULT_RT_EXPIRATION_TIME = 24 * 3600
10
10
  DEFAULT_AT_EXPIRATION_TIME = 1200
11
11
 
12
12
 
13
- def getIdProviderClient(userGroup: str, idProviderClientName: str = None):
13
+ def getIdProviderClient(userGroup: str, idProviderClientName: str = None, client_name_prefix: str = ""):
14
14
  """Get an IdProvider client
15
15
 
16
16
  :param userGroup: group name
17
17
  :param idProviderClientName: name of an identity provider in the DIRAC CS
18
+ :param client_name_prefix: prefix of the client in the CS options
18
19
  """
19
20
  # Get IdProvider credentials from CS
20
21
  if not idProviderClientName and userGroup:
@@ -23,7 +24,7 @@ def getIdProviderClient(userGroup: str, idProviderClientName: str = None):
23
24
  return S_ERROR(f"The {userGroup} group belongs to the VO that is not tied to any Identity Provider.")
24
25
 
25
26
  # Prepare the client instance of the appropriate IdP
26
- return IdProviderFactory().getIdProvider(idProviderClientName)
27
+ return IdProviderFactory().getIdProvider(idProviderClientName, client_name_prefix=client_name_prefix)
27
28
 
28
29
 
29
30
  def getCachedKey(
@@ -1,16 +1,11 @@
1
- import random
2
1
  import requests
3
2
 
4
3
  from cachetools import TTLCache, LRUCache, cached
5
4
  from cachetools.keys import hashkey
6
5
  from pathlib import Path
7
- from requests.adapters import HTTPAdapter
8
6
  from tempfile import NamedTemporaryFile
9
7
  from typing import Any
10
8
  from collections.abc import Generator
11
- from urllib3 import PoolManager
12
- from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
13
-
14
9
  from DIRAC import gConfig
15
10
  from DIRAC.ConfigurationSystem.Client.Helpers import Registry
16
11
  from contextlib import contextmanager
@@ -31,74 +26,7 @@ except ImportError:
31
26
  DEFAULT_TOKEN_CACHE_TTL = 5 * 60
32
27
  DEFAULT_TOKEN_CACHE_SIZE = 1024
33
28
 
34
- # Number of pools to use for a given host.
35
- # It should be in the order of host behind the alias
36
- SESSION_NUM_POOLS = 20
37
- # Number of connection per Pool
38
- SESSION_CONNECTION_POOL_MAX_SIZE = 10
39
-
40
-
41
- class RandomizedPoolManager(PoolManager):
42
- """
43
- A PoolManager subclass that creates multiple connection pools per host.
44
- Each connection request randomly picks one of the available pools.
45
- """
46
-
47
- def __init__(self, num_pools=3, **kwargs):
48
- self.num_pools = num_pools
49
- super().__init__(**kwargs)
50
-
51
- def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
52
- # Pick a random index to diversify the pool key.
53
-
54
- rand_index = random.randint(0, self.num_pools - 1)
55
- pool_key = (f"{host}-{rand_index}", port, scheme)
56
- if pool_key in self.pools:
57
- return self.pools[pool_key]
58
-
59
- # Create a new pool if none exists for this key.
60
- if scheme == "http":
61
- self.pools[pool_key] = HTTPConnectionPool(host, port, **self.connection_pool_kw)
62
- elif scheme == "https":
63
- self.pools[pool_key] = HTTPSConnectionPool(host, port, **self.connection_pool_kw)
64
- else:
65
- raise ValueError(f"Unsupported scheme: {scheme}")
66
-
67
- return self.pools[pool_key]
68
-
69
-
70
- class RandomizedHTTPAdapter(HTTPAdapter):
71
- """
72
- An HTTPAdapter that uses the RandomizedPoolManager.
73
- """
74
-
75
- def __init__(self, num_pools=3, maxsize=10, **kwargs):
76
- self.num_pools = num_pools
77
- self.custom_maxsize = maxsize
78
- super().__init__(**kwargs)
79
-
80
- def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
81
- """
82
- Initialize the pool manager with our custom RandomizedPoolManager.
83
- """
84
- # This ends up being passed to the HTTP(s)ConnectionPool constructors
85
- pool_kwargs.update(
86
- {
87
- "maxsize": self.custom_maxsize,
88
- "block": block,
89
- }
90
- )
91
- self.poolmanager = RandomizedPoolManager(**pool_kwargs)
92
-
93
-
94
- # Create a requests session.
95
- diracx_session = requests.Session()
96
- # Create an instance of the custom adapter.
97
- diracx_pool_adapter = RandomizedHTTPAdapter(num_pools=SESSION_NUM_POOLS, maxsize=SESSION_CONNECTION_POOL_MAX_SIZE)
98
-
99
- # Mount the adapter to handle both HTTP and HTTPS.
100
- diracx_session.mount("http://", diracx_pool_adapter)
101
- diracx_session.mount("https://", diracx_pool_adapter)
29
+ legacy_exchange_session = requests.Session()
102
30
 
103
31
 
104
32
  def get_token(
@@ -117,7 +45,7 @@ def get_token(
117
45
  vo = Registry.getVOForGroup(group)
118
46
  scopes = [f"vo:{vo}", f"group:{group}"] + [f"property:{prop}" for prop in dirac_properties]
119
47
 
120
- r = diracx_session.get(
48
+ r = legacy_exchange_session.get(
121
49
  f"{diracxUrl}/api/auth/legacy-exchange",
122
50
  params={
123
51
  "preferred_username": username,
@@ -333,13 +333,13 @@ class AuthServer(_AuthorizationServer):
333
333
  """Parse request. Rewrite authlib method."""
334
334
  return self.create_oauth2_request(request, JsonRequest, True)
335
335
 
336
- def validate_requested_scope(self, scope, state=None):
336
+ def validate_requested_scope(self, scope):
337
337
  """See :func:`authlib.oauth2.rfc6749.authorization_server.validate_requested_scope`"""
338
338
  # We also consider parametric scope containing ":" charter
339
339
  extended_scope = list_to_scope(
340
340
  [re.sub(r":.*$", ":", s) for s in scope_to_list((scope or "").replace("+", " "))]
341
341
  )
342
- super().validate_requested_scope(extended_scope, state)
342
+ super().validate_requested_scope(extended_scope)
343
343
 
344
344
  def handle_response(self, status_code=None, payload=None, headers=None, newSession=None, delSession=None):
345
345
  """Handle response
@@ -38,8 +38,8 @@ Script.disableCS()
38
38
  from DIRAC.ConfigurationSystem.Client.Helpers.Registry import (
39
39
  findDefaultGroupForDN,
40
40
  getGroupOption,
41
+ getVOForGroup,
41
42
  getVOMSAttributeForGroup,
42
- getVOMSVOForGroup,
43
43
  )
44
44
  from DIRAC.FrameworkSystem.Client.ProxyManagerClient import gProxyManager
45
45
  from DIRAC.FrameworkSystem.private.authorization.utils.Tokens import (
@@ -285,7 +285,7 @@ class Params:
285
285
  if not (vomsAttr := getVOMSAttributeForGroup(self.group)):
286
286
  print(HTML(f"<yellow>No VOMS attribute foud for {self.group}</yellow>"))
287
287
  else:
288
- vo = getVOMSVOForGroup(self.group)
288
+ vo = getVOForGroup(self.group)
289
289
  if not (result := VOMS().setVOMSAttributes(self.outputFile, attribute=vomsAttr, vo=vo))["OK"]:
290
290
  return S_ERROR(f"Failed adding VOMS attribute: {result['Message']}")
291
291
  chain = result["Value"]
@@ -96,7 +96,7 @@ class ProxyInit:
96
96
  )
97
97
 
98
98
  resultVomsAttributes = VOMS.VOMS().setVOMSAttributes(
99
- self.__proxyGenerated, attribute=vomsAttr, vo=Registry.getVOMSVOForGroup(self.__piParams.diracGroup)
99
+ self.__proxyGenerated, attribute=vomsAttr, vo=Registry.getVOForGroup(self.__piParams.diracGroup)
100
100
  )
101
101
  if not resultVomsAttributes["OK"]:
102
102
  return S_ERROR(
@@ -46,6 +46,7 @@ from DIRAC.WorkloadManagementSystem.Client import JobStatus
46
46
  from DIRAC.WorkloadManagementSystem.Client.JobMonitoringClient import JobMonitoringClient
47
47
  from DIRAC.WorkloadManagementSystem.Client.SandboxStoreClient import SandboxStoreClient
48
48
  from DIRAC.WorkloadManagementSystem.Client.WMSClient import WMSClient
49
+ from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import _filterJobStateTransition
49
50
 
50
51
 
51
52
  def parseArguments(args):
@@ -421,6 +422,14 @@ class Dirac(API):
421
422
  sandbox = [isFile.strip() for isFile in sandbox.split(",")]
422
423
  for isFile in sandbox:
423
424
  self.log.debug(f"Resolving Input Sandbox {isFile}")
425
+ # If the sandbox is already in the sandbox store, download it
426
+ if isFile.startswith("SB:"):
427
+ result = SandboxStoreClient(useCertificates=self.useCertificates).downloadSandbox(
428
+ isFile, destinationDir=os.getcwd()
429
+ )
430
+ if not result["OK"]:
431
+ return S_ERROR(f"Cannot download Input sandbox {isFile}: {result['Message']}")
432
+ continue
424
433
  if isFile.lower().startswith("lfn:"): # isFile is an LFN
425
434
  isFile = isFile[4:]
426
435
  # Attempt to copy into job working directory, unless it is already there
@@ -1450,10 +1459,12 @@ class Dirac(API):
1450
1459
  # Remove any job IDs that can't change to the Killed or Deleted states
1451
1460
  filteredJobs = set()
1452
1461
  for filterState in (JobStatus.KILLED, JobStatus.DELETED):
1453
- filterRes = JobStatus.filterJobStateTransition(jobIDs, filterState)
1454
- if not filterRes["OK"]:
1455
- return filterRes
1456
- filteredJobs.update(filterRes["Value"])
1462
+ # get a dictionary of jobID:status
1463
+ res = JobMonitoringClient().getJobsStatus(jobIDs)
1464
+ if not res["OK"]:
1465
+ return res
1466
+ # then filter
1467
+ filteredJobs.update(_filterJobStateTransition(res["Value"], filterState))
1457
1468
 
1458
1469
  return WMSClient(useCertificates=self.useCertificates).deleteJob(list(filteredJobs))
1459
1470
 
@@ -1480,11 +1491,12 @@ class Dirac(API):
1480
1491
  return ret
1481
1492
  jobIDs = ret["Value"]
1482
1493
 
1483
- # Remove any job IDs that can't change to the rescheduled state
1484
- filterRes = JobStatus.filterJobStateTransition(jobIDs, JobStatus.RESCHEDULED)
1485
- if not filterRes["OK"]:
1486
- return filterRes
1487
- jobIDsToReschedule = filterRes["Value"]
1494
+ # get a dictionary of jobID:status
1495
+ res = JobMonitoringClient().getJobsStatus(jobIDs)
1496
+ if not res["OK"]:
1497
+ return res
1498
+ # then filter
1499
+ jobIDsToReschedule = _filterJobStateTransition(res["Value"], JobStatus.RESCHEDULED)
1488
1500
 
1489
1501
  return WMSClient(useCertificates=self.useCertificates).rescheduleJob(jobIDsToReschedule)
1490
1502
 
@@ -1510,10 +1522,12 @@ class Dirac(API):
1510
1522
  # Remove any job IDs that can't change to the Killed or Deleted states
1511
1523
  filteredJobs = set()
1512
1524
  for filterState in (JobStatus.KILLED, JobStatus.DELETED):
1513
- filterRes = JobStatus.filterJobStateTransition(jobIDs, filterState)
1514
- if not filterRes["OK"]:
1515
- return filterRes
1516
- filteredJobs.update(filterRes["Value"])
1525
+ # get a dictionary of jobID:status
1526
+ res = JobMonitoringClient().getJobsStatus(jobIDs)
1527
+ if not res["OK"]:
1528
+ return res
1529
+ # then filter
1530
+ filteredJobs.update(_filterJobStateTransition(res["Value"], filterState))
1517
1531
 
1518
1532
  return WMSClient(useCertificates=self.useCertificates).killJob(list(filteredJobs))
1519
1533