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.
- 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 +38 -26
- 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 +32 -19
- DIRAC/ConfigurationSystem/Client/test/Test_PathFinder.py +41 -1
- DIRAC/ConfigurationSystem/private/RefresherBase.py +4 -2
- DIRAC/Core/Base/API.py +4 -7
- DIRAC/Core/Base/SQLAlchemyDB.py +1 -0
- 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/DISET/private/Transports/SSL/M2Utils.py +3 -1
- DIRAC/Core/LCG/GOCDBClient.py +5 -7
- DIRAC/Core/Security/DiracX.py +31 -17
- DIRAC/Core/Security/IAMService.py +5 -10
- DIRAC/Core/Security/Locations.py +27 -18
- DIRAC/Core/Security/ProxyInfo.py +9 -5
- DIRAC/Core/Security/VOMSService.py +2 -4
- DIRAC/Core/Security/m2crypto/X509Certificate.py +4 -6
- DIRAC/Core/Security/m2crypto/asn1_utils.py +17 -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/CGroups2.py +328 -0
- 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/File.py +1 -1
- DIRAC/Core/Utilities/Graphs/GraphData.py +1 -1
- DIRAC/Core/Utilities/Graphs/GraphUtilities.py +6 -1
- DIRAC/Core/Utilities/JDL.py +1 -195
- DIRAC/Core/Utilities/List.py +1 -124
- DIRAC/Core/Utilities/MySQL.py +103 -99
- DIRAC/Core/Utilities/Os.py +32 -1
- DIRAC/Core/Utilities/Platform.py +2 -107
- DIRAC/Core/Utilities/Proxy.py +0 -4
- DIRAC/Core/Utilities/ReturnValues.py +7 -252
- DIRAC/Core/Utilities/StateMachine.py +12 -178
- DIRAC/Core/Utilities/Subprocess.py +35 -14
- 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 +72 -46
- DIRAC/Core/scripts/dirac_configure.py +1 -3
- DIRAC/Core/scripts/dirac_install_db.py +24 -6
- 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 +7 -3
- DIRAC/DataManagementSystem/DB/FileCatalogComponents/DatasetManager/DatasetManager.py +1 -1
- DIRAC/DataManagementSystem/DB/FileCatalogDB.sql +9 -9
- DIRAC/DataManagementSystem/DB/FileCatalogWithFkAndPsDB.sql +9 -9
- DIRAC/DataManagementSystem/Utilities/DMSHelpers.py +6 -2
- DIRAC/DataManagementSystem/scripts/dirac_admin_allow_se.py +13 -8
- DIRAC/DataManagementSystem/scripts/dirac_admin_ban_se.py +13 -8
- DIRAC/DataManagementSystem/scripts/dirac_dms_create_moving_request.py +2 -0
- DIRAC/DataManagementSystem/scripts/dirac_dms_protocol_matrix.py +0 -1
- DIRAC/FrameworkSystem/Client/BundleDeliveryClient.py +2 -7
- DIRAC/FrameworkSystem/Client/ComponentInstaller.py +9 -4
- DIRAC/FrameworkSystem/Client/ProxyManagerClient.py +5 -2
- DIRAC/FrameworkSystem/Client/SystemAdministratorClientCLI.py +11 -6
- DIRAC/FrameworkSystem/ConfigTemplate.cfg +2 -0
- DIRAC/FrameworkSystem/DB/AuthDB.py +3 -3
- DIRAC/FrameworkSystem/DB/InstalledComponentsDB.py +4 -4
- DIRAC/FrameworkSystem/DB/ProxyDB.py +11 -3
- DIRAC/FrameworkSystem/DB/TokenDB.py +1 -1
- DIRAC/FrameworkSystem/Service/ProxyManagerHandler.py +8 -6
- DIRAC/FrameworkSystem/Utilities/MonitoringUtilities.py +2 -19
- DIRAC/FrameworkSystem/Utilities/TokenManagementUtilities.py +3 -2
- DIRAC/FrameworkSystem/Utilities/diracx.py +36 -14
- DIRAC/FrameworkSystem/private/authorization/AuthServer.py +2 -2
- DIRAC/FrameworkSystem/scripts/dirac_admin_update_pilot.py +18 -11
- DIRAC/FrameworkSystem/scripts/dirac_login.py +2 -2
- DIRAC/FrameworkSystem/scripts/dirac_proxy_init.py +7 -8
- DIRAC/Interfaces/API/Dirac.py +27 -15
- DIRAC/Interfaces/API/DiracAdmin.py +45 -17
- DIRAC/Interfaces/API/Job.py +9 -13
- DIRAC/Interfaces/scripts/dirac_admin_allow_site.py +12 -18
- DIRAC/Interfaces/scripts/dirac_admin_ban_site.py +12 -10
- DIRAC/Interfaces/scripts/dirac_admin_get_site_mask.py +4 -13
- DIRAC/Interfaces/scripts/dirac_admin_reset_job.py +3 -6
- 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/DB/ProductionDB.sql +4 -4
- DIRAC/ProductionSystem/scripts/dirac_prod_get.py +2 -2
- DIRAC/ProductionSystem/scripts/dirac_prod_get_all.py +2 -2
- DIRAC/ProductionSystem/scripts/dirac_prod_get_trans.py +2 -3
- DIRAC/RequestManagementSystem/Agent/RequestExecutingAgent.py +8 -6
- DIRAC/RequestManagementSystem/Agent/RequestOperations/ForwardDISET.py +2 -14
- DIRAC/RequestManagementSystem/Client/ReqClient.py +66 -13
- DIRAC/RequestManagementSystem/ConfigTemplate.cfg +6 -6
- DIRAC/RequestManagementSystem/DB/RequestDB.py +10 -5
- DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
- DIRAC/RequestManagementSystem/private/RequestValidator.py +40 -46
- DIRAC/ResourceStatusSystem/Client/SiteStatus.py +4 -2
- DIRAC/ResourceStatusSystem/Command/FreeDiskSpaceCommand.py +3 -1
- DIRAC/ResourceStatusSystem/DB/ResourceManagementDB.py +8 -8
- DIRAC/ResourceStatusSystem/DB/ResourceStatusDB.py +2 -2
- DIRAC/ResourceStatusSystem/Utilities/CSHelpers.py +2 -31
- DIRAC/ResourceStatusSystem/scripts/dirac_rss_set_status.py +30 -12
- DIRAC/Resources/Catalog/RucioFileCatalogClient.py +195 -1
- DIRAC/Resources/Catalog/test/Test_RucioFileCatalogClient.py +181 -0
- DIRAC/Resources/Computing/AREXComputingElement.py +25 -8
- 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/ComputingElement.py +1 -1
- DIRAC/Resources/Computing/HTCondorCEComputingElement.py +44 -44
- DIRAC/Resources/Computing/InProcessComputingElement.py +4 -2
- DIRAC/Resources/Computing/LocalComputingElement.py +1 -18
- DIRAC/Resources/Computing/SSHBatchComputingElement.py +1 -17
- DIRAC/Resources/Computing/SSHComputingElement.py +1 -18
- DIRAC/Resources/Computing/SingularityComputingElement.py +19 -5
- 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/MessageQueue/StompMQConnector.py +1 -1
- DIRAC/Resources/Storage/GFAL2_StorageBase.py +24 -15
- DIRAC/Resources/Storage/OccupancyPlugins/WLCGAccountingHTTPJson.py +1 -3
- DIRAC/Resources/Storage/StorageBase.py +4 -2
- DIRAC/Resources/Storage/StorageElement.py +6 -7
- DIRAC/StorageManagementSystem/DB/StorageManagementDB.sql +2 -2
- DIRAC/TransformationSystem/Agent/TaskManagerAgentBase.py +10 -16
- DIRAC/TransformationSystem/Agent/TransformationAgent.py +22 -1
- DIRAC/TransformationSystem/Agent/TransformationCleaningAgent.py +16 -16
- DIRAC/TransformationSystem/Client/TaskManager.py +2 -4
- DIRAC/TransformationSystem/Client/Transformation.py +6 -7
- DIRAC/TransformationSystem/Client/TransformationClient.py +21 -11
- DIRAC/TransformationSystem/Client/Utilities.py +9 -0
- DIRAC/TransformationSystem/DB/TransformationDB.py +11 -14
- DIRAC/TransformationSystem/DB/TransformationDB.sql +9 -9
- DIRAC/TransformationSystem/Service/TransformationManagerHandler.py +0 -333
- DIRAC/TransformationSystem/Utilities/ReplicationCLIParameters.py +3 -3
- DIRAC/TransformationSystem/Utilities/TransformationInfo.py +7 -5
- DIRAC/TransformationSystem/scripts/dirac_production_runjoblocal.py +2 -4
- DIRAC/TransformationSystem/test/Test_TransformationInfo.py +22 -15
- DIRAC/TransformationSystem/test/Test_replicationTransformation.py +5 -6
- DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
- DIRAC/WorkloadManagementSystem/Agent/JobAgent.py +38 -26
- DIRAC/WorkloadManagementSystem/Agent/JobCleaningAgent.py +12 -8
- DIRAC/WorkloadManagementSystem/Agent/PilotSyncAgent.py +4 -3
- DIRAC/WorkloadManagementSystem/Agent/PushJobAgent.py +13 -13
- DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +18 -14
- DIRAC/WorkloadManagementSystem/Agent/StalledJobAgent.py +18 -51
- DIRAC/WorkloadManagementSystem/Agent/StatesAccountingAgent.py +41 -1
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +45 -4
- 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 +9 -2
- DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_StalledJobAgent.py +4 -5
- DIRAC/WorkloadManagementSystem/Client/DownloadInputData.py +9 -9
- DIRAC/WorkloadManagementSystem/Client/InputDataResolution.py +6 -6
- DIRAC/WorkloadManagementSystem/Client/JobMonitoringClient.py +10 -11
- DIRAC/WorkloadManagementSystem/Client/JobReport.py +1 -1
- DIRAC/WorkloadManagementSystem/Client/JobState/CachedJobState.py +3 -0
- DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
- DIRAC/WorkloadManagementSystem/Client/JobState/JobState.py +6 -0
- DIRAC/WorkloadManagementSystem/Client/JobStateUpdateClient.py +3 -0
- DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -152
- DIRAC/WorkloadManagementSystem/Client/PoolXMLSlice.py +12 -19
- 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 +89 -132
- DIRAC/WorkloadManagementSystem/DB/JobDB.sql +8 -8
- DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
- DIRAC/WorkloadManagementSystem/DB/JobLoggingDB.py +19 -6
- DIRAC/WorkloadManagementSystem/DB/JobParametersDB.py +9 -9
- DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.py +16 -5
- DIRAC/WorkloadManagementSystem/DB/PilotAgentsDB.sql +3 -3
- DIRAC/WorkloadManagementSystem/DB/SandboxMetadataDB.py +44 -82
- 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 +5 -4
- DIRAC/WorkloadManagementSystem/Executor/JobScheduling.py +4 -0
- DIRAC/WorkloadManagementSystem/FutureClient/JobStateUpdateClient.py +75 -33
- DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +22 -11
- DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapperTemplate.py +9 -10
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +60 -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 -103
- DIRAC/WorkloadManagementSystem/Service/SandboxStoreHandler.py +7 -53
- DIRAC/WorkloadManagementSystem/Service/WMSAdministratorHandler.py +16 -79
- DIRAC/WorkloadManagementSystem/Service/WMSUtilities.py +4 -18
- DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -209
- 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 +41 -11
- 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 -15
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
- DIRAC/WorkloadManagementSystem/Utilities/test/Test_PilotWrapper.py +16 -0
- DIRAC/WorkloadManagementSystem/scripts/dirac_jobexec.py +7 -2
- DIRAC/WorkloadManagementSystem/scripts/dirac_wms_pilot_job_info.py +1 -1
- DIRAC/__init__.py +62 -60
- DIRAC/tests/Utilities/testJobDefinitions.py +22 -28
- {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/METADATA +8 -5
- {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/RECORD +229 -228
- {DIRAC-9.0.0a42.dist-info → dirac-9.0.7.dist-info}/WHEEL +1 -1
- {DIRAC-9.0.0a42.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/Resources/Computing/PilotBundle.py +0 -70
- 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.0a42.dist-info → dirac-9.0.7.dist-info/licenses}/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
% (
|
|
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=
|
|
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 =
|
|
219
|
-
if
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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.
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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}")
|