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.
- DIRAC/AccountingSystem/Client/AccountingCLI.py +0 -140
- DIRAC/AccountingSystem/Client/DataStoreClient.py +0 -13
- DIRAC/AccountingSystem/Client/Types/BaseAccountingType.py +0 -7
- DIRAC/AccountingSystem/ConfigTemplate.cfg +0 -5
- DIRAC/AccountingSystem/Service/DataStoreHandler.py +0 -72
- DIRAC/ConfigurationSystem/Client/Helpers/CSGlobals.py +0 -9
- DIRAC/ConfigurationSystem/Client/Helpers/Registry.py +34 -32
- DIRAC/ConfigurationSystem/Client/Helpers/Resources.py +11 -43
- DIRAC/ConfigurationSystem/Client/Helpers/test/Test_Helpers.py +0 -16
- DIRAC/ConfigurationSystem/Client/LocalConfiguration.py +14 -8
- DIRAC/ConfigurationSystem/Client/PathFinder.py +47 -8
- DIRAC/ConfigurationSystem/Client/SyncPlugins/CERNLDAPSyncPlugin.py +4 -1
- DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +9 -2
- DIRAC/ConfigurationSystem/Client/test/Test_PathFinder.py +41 -1
- DIRAC/ConfigurationSystem/private/RefresherBase.py +4 -2
- DIRAC/Core/DISET/ServiceReactor.py +11 -3
- DIRAC/Core/DISET/private/BaseClient.py +1 -2
- DIRAC/Core/DISET/private/Transports/M2SSLTransport.py +9 -7
- DIRAC/Core/Security/DiracX.py +12 -7
- DIRAC/Core/Security/IAMService.py +4 -3
- DIRAC/Core/Security/ProxyInfo.py +9 -5
- DIRAC/Core/Security/test/test_diracx_token_from_pem.py +161 -0
- DIRAC/Core/Tornado/Client/ClientSelector.py +4 -1
- DIRAC/Core/Tornado/Server/TornadoService.py +1 -1
- DIRAC/Core/Utilities/ClassAd/ClassAdLight.py +4 -290
- DIRAC/Core/Utilities/DErrno.py +5 -309
- DIRAC/Core/Utilities/Extensions.py +10 -1
- DIRAC/Core/Utilities/Graphs/GraphData.py +1 -1
- DIRAC/Core/Utilities/JDL.py +1 -195
- DIRAC/Core/Utilities/List.py +1 -124
- DIRAC/Core/Utilities/MySQL.py +101 -97
- DIRAC/Core/Utilities/Os.py +32 -1
- DIRAC/Core/Utilities/Platform.py +2 -107
- DIRAC/Core/Utilities/ReturnValues.py +7 -252
- DIRAC/Core/Utilities/StateMachine.py +12 -178
- DIRAC/Core/Utilities/TimeUtilities.py +10 -253
- DIRAC/Core/Utilities/test/Test_JDL.py +0 -3
- DIRAC/Core/Utilities/test/Test_Profiler.py +20 -20
- DIRAC/Core/scripts/dirac_agent.py +1 -1
- DIRAC/Core/scripts/dirac_apptainer_exec.py +16 -7
- DIRAC/Core/scripts/dirac_platform.py +1 -92
- DIRAC/DataManagementSystem/Agent/FTS3Agent.py +8 -7
- DIRAC/DataManagementSystem/Agent/RequestOperations/RemoveFile.py +7 -6
- DIRAC/DataManagementSystem/Client/FTS3Job.py +71 -34
- DIRAC/DataManagementSystem/DB/FTS3DB.py +3 -0
- DIRAC/DataManagementSystem/DB/FileCatalogComponents/DatasetManager/DatasetManager.py +1 -1
- DIRAC/DataManagementSystem/Utilities/DMSHelpers.py +6 -2
- DIRAC/DataManagementSystem/scripts/dirac_dms_create_moving_request.py +2 -0
- DIRAC/DataManagementSystem/scripts/dirac_dms_protocol_matrix.py +0 -1
- DIRAC/FrameworkSystem/Client/ComponentInstaller.py +4 -2
- DIRAC/FrameworkSystem/DB/ProxyDB.py +9 -5
- DIRAC/FrameworkSystem/Utilities/TokenManagementUtilities.py +3 -2
- DIRAC/FrameworkSystem/Utilities/diracx.py +2 -74
- DIRAC/FrameworkSystem/private/authorization/AuthServer.py +2 -2
- DIRAC/FrameworkSystem/scripts/dirac_login.py +2 -2
- DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +1 -1
- DIRAC/Interfaces/API/Dirac.py +27 -13
- DIRAC/Interfaces/API/DiracAdmin.py +42 -7
- DIRAC/Interfaces/API/Job.py +1 -0
- DIRAC/Interfaces/scripts/dirac_admin_allow_site.py +7 -1
- DIRAC/Interfaces/scripts/dirac_admin_ban_site.py +7 -1
- DIRAC/Interfaces/scripts/dirac_wms_job_parameters.py +0 -1
- DIRAC/MonitoringSystem/Client/Types/WMSHistory.py +4 -0
- DIRAC/MonitoringSystem/Client/WebAppClient.py +26 -0
- DIRAC/MonitoringSystem/ConfigTemplate.cfg +9 -0
- DIRAC/MonitoringSystem/DB/MonitoringDB.py +6 -25
- DIRAC/MonitoringSystem/Service/MonitoringHandler.py +0 -33
- DIRAC/MonitoringSystem/Service/WebAppHandler.py +599 -0
- DIRAC/MonitoringSystem/private/MainReporter.py +0 -3
- DIRAC/ProductionSystem/scripts/dirac_prod_get_trans.py +2 -3
- DIRAC/RequestManagementSystem/Agent/RequestExecutingAgent.py +8 -6
- DIRAC/RequestManagementSystem/ConfigTemplate.cfg +6 -6
- DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
- DIRAC/ResourceStatusSystem/Client/SiteStatus.py +4 -2
- DIRAC/ResourceStatusSystem/Command/FreeDiskSpaceCommand.py +3 -1
- DIRAC/ResourceStatusSystem/Utilities/CSHelpers.py +2 -31
- DIRAC/ResourceStatusSystem/scripts/dirac_rss_set_status.py +18 -4
- DIRAC/Resources/Catalog/RucioFileCatalogClient.py +1 -1
- DIRAC/Resources/Computing/AREXComputingElement.py +19 -3
- DIRAC/Resources/Computing/BatchSystems/Condor.py +126 -108
- DIRAC/Resources/Computing/BatchSystems/SLURM.py +5 -1
- DIRAC/Resources/Computing/BatchSystems/test/Test_SLURM.py +46 -0
- DIRAC/Resources/Computing/HTCondorCEComputingElement.py +37 -43
- DIRAC/Resources/Computing/SingularityComputingElement.py +6 -1
- DIRAC/Resources/Computing/test/Test_HTCondorCEComputingElement.py +67 -49
- DIRAC/Resources/Computing/test/Test_PoolComputingElement.py +2 -1
- DIRAC/Resources/IdProvider/CheckInIdProvider.py +13 -0
- DIRAC/Resources/IdProvider/IdProviderFactory.py +11 -3
- DIRAC/Resources/Storage/StorageBase.py +4 -2
- DIRAC/Resources/Storage/StorageElement.py +4 -4
- DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +10 -16
- DIRAC/TransformationSystem/Agent/TransformationAgent.py +22 -1
- DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +15 -15
- DIRAC/TransformationSystem/Client/Transformation.py +2 -1
- DIRAC/TransformationSystem/Client/TransformationClient.py +0 -7
- DIRAC/TransformationSystem/Client/Utilities.py +9 -0
- DIRAC/TransformationSystem/Service/TransformationManagerHandler.py +0 -336
- DIRAC/TransformationSystem/Utilities/ReplicationCLIParameters.py +3 -3
- DIRAC/TransformationSystem/scripts/dirac_production_runjoblocal.py +2 -4
- DIRAC/TransformationSystem/test/Test_replicationTransformation.py +5 -6
- DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
- DIRAC/WorkloadManagementSystem/Agent/JobAgent.py +1 -5
- DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +11 -7
- DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py +4 -3
- DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py +13 -13
- DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +10 -13
- DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +18 -51
- DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py +41 -1
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +2 -0
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobCleaningAgent.py +7 -9
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PushJobAgent.py +1 -0
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_SiteDirector.py +8 -2
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +4 -5
- DIRAC/WorkloadManagementSystem/Client/DownloadInputData.py +7 -5
- DIRAC/WorkloadManagementSystem/Client/JobMonitoringClient.py +10 -11
- DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
- DIRAC/WorkloadManagementSystem/Client/JobStateUpdateClient.py +3 -0
- DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -152
- DIRAC/WorkloadManagementSystem/Client/SandboxStoreClient.py +25 -38
- DIRAC/WorkloadManagementSystem/Client/WMSClient.py +2 -3
- DIRAC/WorkloadManagementSystem/Client/test/Test_Client_DownloadInputData.py +29 -0
- DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg +4 -8
- DIRAC/WorkloadManagementSystem/DB/JobDB.py +40 -69
- DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
- DIRAC/WorkloadManagementSystem/DB/JobParametersDB.py +9 -9
- DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.py +3 -2
- DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +28 -39
- DIRAC/WorkloadManagementSystem/DB/StatusUtils.py +125 -0
- DIRAC/WorkloadManagementSystem/DB/tests/Test_JobDB.py +1 -1
- DIRAC/WorkloadManagementSystem/DB/tests/Test_StatusUtils.py +28 -0
- DIRAC/WorkloadManagementSystem/Executor/JobSanity.py +3 -3
- DIRAC/WorkloadManagementSystem/FutureClient/JobStateUpdateClient.py +2 -14
- DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +14 -9
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +36 -10
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py +4 -0
- DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +33 -154
- DIRAC/WorkloadManagementSystem/Service/JobMonitoringHandler.py +5 -323
- DIRAC/WorkloadManagementSystem/Service/JobStateUpdateHandler.py +0 -16
- DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py +6 -102
- DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +5 -51
- DIRAC/WorkloadManagementSystem/Service/WMSAdministratorHandler.py +16 -79
- DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -199
- DIRAC/WorkloadManagementSystem/Utilities/JobParameters.py +65 -3
- DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py +2 -64
- DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py +7 -171
- DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py +73 -7
- DIRAC/WorkloadManagementSystem/Utilities/PilotWrapper.py +2 -0
- DIRAC/WorkloadManagementSystem/Utilities/RemoteRunner.py +16 -0
- DIRAC/WorkloadManagementSystem/Utilities/Utils.py +36 -1
- DIRAC/WorkloadManagementSystem/Utilities/jobAdministration.py +15 -0
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py +1 -5
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_PilotWrapper.py +16 -0
- DIRAC/__init__.py +55 -54
- {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/METADATA +6 -4
- {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/RECORD +160 -160
- {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/WHEEL +1 -1
- {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/entry_points.txt +0 -3
- DIRAC/Core/Utilities/test/Test_List.py +0 -150
- DIRAC/Core/Utilities/test/Test_Time.py +0 -88
- DIRAC/TransformationSystem/scripts/dirac_transformation_archive.py +0 -30
- DIRAC/TransformationSystem/scripts/dirac_transformation_clean.py +0 -30
- DIRAC/TransformationSystem/scripts/dirac_transformation_remove_output.py +0 -30
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobManager.py +0 -58
- {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/licenses/LICENSE +0 -0
- {dirac-9.0.0a54.dist-info → dirac-9.0.7.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
"""
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
513
|
-
|
|
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
|
-
|
|
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 =
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
|
886
|
-
"""This methods generates the necessary information to create
|
|
887
|
-
accounting
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
@
|
|
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, "
|
|
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, "
|
|
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"]["
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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.
|
|
99
|
+
self.__proxyGenerated, attribute=vomsAttr, vo=Registry.getVOForGroup(self.__piParams.diracGroup)
|
|
100
100
|
)
|
|
101
101
|
if not resultVomsAttributes["OK"]:
|
|
102
102
|
return S_ERROR(
|
DIRAC/Interfaces/API/Dirac.py
CHANGED
|
@@ -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
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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
|
-
#
|
|
1484
|
-
|
|
1485
|
-
if not
|
|
1486
|
-
return
|
|
1487
|
-
|
|
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
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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
|
|