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
|
@@ -4,176 +4,12 @@
|
|
|
4
4
|
getParameterVectorLength() - to get the total size of the bunch of parametric jobs
|
|
5
5
|
generateParametricJobs() - to get a list of expanded descriptions of all the jobs
|
|
6
6
|
"""
|
|
7
|
-
import re
|
|
8
7
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
8
|
+
# Import from DIRACCommon for backward compatibility
|
|
9
|
+
from DIRACCommon.WorkloadManagementSystem.Utilities.ParametricJob import (
|
|
10
|
+
getParameterVectorLength,
|
|
11
|
+
generateParametricJobs,
|
|
12
|
+
)
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if parList:
|
|
16
|
-
if nPar != len(parList):
|
|
17
|
-
return []
|
|
18
|
-
else:
|
|
19
|
-
parameterList = list(parList)
|
|
20
|
-
else:
|
|
21
|
-
# The first parameter must have the same type as the other ones even if not defined explicitly
|
|
22
|
-
parameterList = [parStart * type(parFactor)(1) + type(parStep)(0)]
|
|
23
|
-
for np in range(1, nPar):
|
|
24
|
-
parameterList.append(parameterList[np - 1] * parFactor + parStep)
|
|
25
|
-
|
|
26
|
-
return parameterList
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def getParameterVectorLength(jobClassAd):
|
|
30
|
-
"""Get the length of parameter vector in the parametric job description
|
|
31
|
-
|
|
32
|
-
:param jobClassAd: ClassAd job description object
|
|
33
|
-
:return: result structure with the Value: int number of parameter values, None if not a parametric job
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
nParValues = None
|
|
37
|
-
attributes = jobClassAd.getAttributes()
|
|
38
|
-
for attribute in attributes:
|
|
39
|
-
if attribute.startswith("Parameters"):
|
|
40
|
-
if jobClassAd.isAttributeList(attribute):
|
|
41
|
-
parameterList = jobClassAd.getListFromExpression(attribute)
|
|
42
|
-
nThisParValues = len(parameterList)
|
|
43
|
-
else:
|
|
44
|
-
nThisParValues = jobClassAd.getAttributeInt(attribute)
|
|
45
|
-
if nParValues is not None and nParValues != nThisParValues:
|
|
46
|
-
return S_ERROR(
|
|
47
|
-
EWMSJDL,
|
|
48
|
-
"Different length of parameter vectors: for %s, %s != %d" % (attribute, nParValues, nThisParValues),
|
|
49
|
-
)
|
|
50
|
-
nParValues = nThisParValues
|
|
51
|
-
if nParValues is not None and nParValues <= 0:
|
|
52
|
-
return S_ERROR(EWMSJDL, "Illegal number of job parameters %d" % (nParValues))
|
|
53
|
-
return S_OK(nParValues)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def __updateAttribute(classAd, attribute, parName, parValue):
|
|
57
|
-
# If there is something to do:
|
|
58
|
-
pattern = r"%%\(%s\)s" % parName
|
|
59
|
-
if parName == "0":
|
|
60
|
-
pattern = "%s"
|
|
61
|
-
expr = classAd.get_expression(attribute)
|
|
62
|
-
if not re.search(pattern, expr):
|
|
63
|
-
return False
|
|
64
|
-
|
|
65
|
-
pattern = "%%(%s)s" % parName
|
|
66
|
-
if parName == "0":
|
|
67
|
-
pattern = "%s"
|
|
68
|
-
|
|
69
|
-
parValue = parValue.strip()
|
|
70
|
-
if classAd.isAttributeList(attribute):
|
|
71
|
-
parValue = parValue.strip()
|
|
72
|
-
if parValue.startswith("{"):
|
|
73
|
-
parValue = parValue.lstrip("{").rstrip("}").strip()
|
|
74
|
-
|
|
75
|
-
expr = classAd.get_expression(attribute)
|
|
76
|
-
newexpr = expr.replace(pattern, str(parValue))
|
|
77
|
-
classAd.set_expression(attribute, newexpr)
|
|
78
|
-
return True
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def generateParametricJobs(jobClassAd):
|
|
82
|
-
"""Generate a series of ClassAd job descriptions expanding
|
|
83
|
-
job parameters
|
|
84
|
-
|
|
85
|
-
:param jobClassAd: ClassAd job description object
|
|
86
|
-
:return: list of ClassAd job description objects
|
|
87
|
-
"""
|
|
88
|
-
if not jobClassAd.lookupAttribute("Parameters"):
|
|
89
|
-
return S_OK([jobClassAd.asJDL()])
|
|
90
|
-
|
|
91
|
-
result = getParameterVectorLength(jobClassAd)
|
|
92
|
-
if not result["OK"]:
|
|
93
|
-
return result
|
|
94
|
-
nParValues = result["Value"]
|
|
95
|
-
if nParValues is None:
|
|
96
|
-
return S_ERROR(EWMSJDL, "Can not determine the number of job parameters")
|
|
97
|
-
|
|
98
|
-
parameterDict = {}
|
|
99
|
-
attributes = jobClassAd.getAttributes()
|
|
100
|
-
for attribute in attributes:
|
|
101
|
-
for key in ["Parameters", "ParameterStart", "ParameterStep", "ParameterFactor"]:
|
|
102
|
-
if attribute.startswith(key):
|
|
103
|
-
seqID = "0" if "." not in attribute else attribute.split(".")[1]
|
|
104
|
-
parameterDict.setdefault(seqID, {})
|
|
105
|
-
if key == "Parameters":
|
|
106
|
-
if jobClassAd.isAttributeList(attribute):
|
|
107
|
-
parList = jobClassAd.getListFromExpression(attribute)
|
|
108
|
-
if len(parList) != nParValues:
|
|
109
|
-
return S_ERROR(EWMSJDL, "Inconsistent parametric job description")
|
|
110
|
-
parameterDict[seqID]["ParameterList"] = parList
|
|
111
|
-
else:
|
|
112
|
-
if attribute != "Parameters":
|
|
113
|
-
return S_ERROR(EWMSJDL, "Inconsistent parametric job description")
|
|
114
|
-
nPar = jobClassAd.getAttributeInt(attribute)
|
|
115
|
-
if nPar is None:
|
|
116
|
-
value = jobClassAd.get_expression(attribute)
|
|
117
|
-
return S_ERROR(EWMSJDL, f"Inconsistent parametric job description: {attribute}={value}")
|
|
118
|
-
parameterDict[seqID]["Parameters"] = nPar
|
|
119
|
-
else:
|
|
120
|
-
value = jobClassAd.getAttributeInt(attribute)
|
|
121
|
-
if value is None:
|
|
122
|
-
value = jobClassAd.getAttributeFloat(attribute)
|
|
123
|
-
if value is None:
|
|
124
|
-
value = jobClassAd.get_expression(attribute)
|
|
125
|
-
return S_ERROR(f"Illegal value for {attribute} JDL field: {value}")
|
|
126
|
-
parameterDict[seqID][key] = value
|
|
127
|
-
|
|
128
|
-
if "0" in parameterDict and not parameterDict.get("0"):
|
|
129
|
-
parameterDict.pop("0")
|
|
130
|
-
|
|
131
|
-
parameterLists = {}
|
|
132
|
-
for seqID in parameterDict:
|
|
133
|
-
parList = __getParameterSequence(
|
|
134
|
-
nParValues,
|
|
135
|
-
parList=parameterDict[seqID].get("ParameterList", []),
|
|
136
|
-
parStart=parameterDict[seqID].get("ParameterStart", 1),
|
|
137
|
-
parStep=parameterDict[seqID].get("ParameterStep", 0),
|
|
138
|
-
parFactor=parameterDict[seqID].get("ParameterFactor", 1),
|
|
139
|
-
)
|
|
140
|
-
if not parList:
|
|
141
|
-
return S_ERROR(EWMSJDL, "Inconsistent parametric job description")
|
|
142
|
-
|
|
143
|
-
parameterLists[seqID] = parList
|
|
144
|
-
|
|
145
|
-
jobDescList = []
|
|
146
|
-
jobDesc = jobClassAd.asJDL()
|
|
147
|
-
# Width of the sequential parameter number
|
|
148
|
-
zLength = len(str(nParValues - 1))
|
|
149
|
-
for n in range(nParValues):
|
|
150
|
-
newJobDesc = jobDesc
|
|
151
|
-
newJobDesc = newJobDesc.replace("%n", str(n).zfill(zLength))
|
|
152
|
-
newClassAd = ClassAd(newJobDesc)
|
|
153
|
-
for seqID in parameterLists:
|
|
154
|
-
parameter = parameterLists[seqID][n]
|
|
155
|
-
for attribute in newClassAd.getAttributes():
|
|
156
|
-
__updateAttribute(newClassAd, attribute, seqID, str(parameter))
|
|
157
|
-
|
|
158
|
-
for seqID in parameterLists:
|
|
159
|
-
for attribute in ["Parameters", "ParameterStart", "ParameterStep", "ParameterFactor"]:
|
|
160
|
-
if seqID == "0":
|
|
161
|
-
newClassAd.deleteAttribute(attribute)
|
|
162
|
-
else:
|
|
163
|
-
newClassAd.deleteAttribute(f"{attribute}.{seqID}")
|
|
164
|
-
|
|
165
|
-
parameter = parameterLists[seqID][n]
|
|
166
|
-
if seqID == "0":
|
|
167
|
-
attribute = "Parameter"
|
|
168
|
-
else:
|
|
169
|
-
attribute = f"Parameter.{seqID}"
|
|
170
|
-
if isinstance(parameter, str) and parameter.startswith("{"):
|
|
171
|
-
newClassAd.insertAttributeInt(attribute, str(parameter))
|
|
172
|
-
else:
|
|
173
|
-
newClassAd.insertAttributeString(attribute, str(parameter))
|
|
174
|
-
|
|
175
|
-
newClassAd.insertAttributeInt("ParameterNumber", n)
|
|
176
|
-
newJDL = newClassAd.asJDL()
|
|
177
|
-
jobDescList.append(newJDL)
|
|
178
|
-
|
|
179
|
-
return S_OK(jobDescList)
|
|
14
|
+
# Re-export for backward compatibility
|
|
15
|
+
__all__ = ["getParameterVectorLength", "generateParametricJobs"]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"""CStoJSONSynchronizer
|
|
2
|
+
Module that keeps the pilot parameters file synchronized with the information
|
|
3
|
+
in the Operations/Pilot section of the CS. If there are additions in the CS,
|
|
4
|
+
these are incorporated to the file.
|
|
5
|
+
The module uploads to a web server the latest version of the pilot scripts.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import datetime
|
|
8
9
|
import glob
|
|
9
10
|
import os
|
|
10
11
|
import shutil
|
|
11
12
|
import tarfile
|
|
12
13
|
from typing import Any
|
|
13
|
-
|
|
14
14
|
from git import Repo
|
|
15
15
|
|
|
16
16
|
from DIRAC import S_OK, gConfig, gLogger
|
|
@@ -19,6 +19,67 @@ from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
|
|
|
19
19
|
from DIRAC.ConfigurationSystem.Client.Helpers.Path import cfgPath
|
|
20
20
|
from DIRAC.Core.Utilities.ReturnValues import DOKReturnType, DReturnType
|
|
21
21
|
|
|
22
|
+
import socket
|
|
23
|
+
from urllib.parse import urlparse
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def exclude_master_cs_aliases(urls: list[str], master_cs_url: str) -> list[str]:
|
|
27
|
+
"""
|
|
28
|
+
Excludes URLs that are DNS aliases of the given MasterCS server URL.
|
|
29
|
+
|
|
30
|
+
This function resolves the IP addresses of the MasterCS server and each URL in the input list.
|
|
31
|
+
It returns a new list containing only those URLs whose hostnames do not resolve to any of the
|
|
32
|
+
MasterCS server's IP addresses, effectively excluding all DNS aliases of the MasterCS server.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
urls (list[str]): A list of URLs to filter. Each URL should be a string in a valid URL format.
|
|
36
|
+
master_cs_url (str): The reference URL (e.g., MasterCS server URL) whose DNS aliases are to be excluded.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
list[str]: A new list of URLs with all aliases of the MasterCS server removed.
|
|
40
|
+
If the MasterCS hostname cannot be resolved, the original list is returned unchanged.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> urls = [
|
|
44
|
+
... 'dips://lbvobox303.cern.ch:9135/Configuration/Server',
|
|
45
|
+
... 'dips://ccwlcglhcb02.in2p3.fr:9135/Configuration/Server',
|
|
46
|
+
... 'dips://lbvobox302.cern.ch:9135/Configuration/Server',
|
|
47
|
+
... ]
|
|
48
|
+
>>> master_cs_url = "dips://mastercs.cern.ch:9135/Configuration/Server"
|
|
49
|
+
>>> exclude_master_cs_aliases(urls, master_cs_url)
|
|
50
|
+
['dips://ccwlcglhcb02.in2p3.fr:9135/Configuration/Server']
|
|
51
|
+
|
|
52
|
+
Notes:
|
|
53
|
+
- If the MasterCS hostname cannot be resolved, the function returns the original list.
|
|
54
|
+
- If a hostname in the input list cannot be resolved, it is included in the result.
|
|
55
|
+
- The comparison is based on IP addresses, not hostnames.
|
|
56
|
+
"""
|
|
57
|
+
master_cs_hostname = urlparse(master_cs_url).hostname
|
|
58
|
+
if not master_cs_hostname:
|
|
59
|
+
return urls
|
|
60
|
+
|
|
61
|
+
# Resolve IP addresses for the MasterCS hostname
|
|
62
|
+
try:
|
|
63
|
+
master_cs_ips = set(socket.gethostbyname_ex(master_cs_hostname)[2])
|
|
64
|
+
except socket.gaierror:
|
|
65
|
+
return urls
|
|
66
|
+
|
|
67
|
+
# Function to get IPs for a hostname
|
|
68
|
+
def get_ips(hostname):
|
|
69
|
+
try:
|
|
70
|
+
return set(socket.gethostbyname_ex(hostname)[2])
|
|
71
|
+
except socket.gaierror:
|
|
72
|
+
return set()
|
|
73
|
+
|
|
74
|
+
filtered_urls = []
|
|
75
|
+
for url in urls:
|
|
76
|
+
hostname = urlparse(url).hostname
|
|
77
|
+
ips = get_ips(hostname)
|
|
78
|
+
if not ips & master_cs_ips:
|
|
79
|
+
filtered_urls.append(url)
|
|
80
|
+
|
|
81
|
+
return filtered_urls
|
|
82
|
+
|
|
22
83
|
|
|
23
84
|
class PilotCStoJSONSynchronizer:
|
|
24
85
|
"""
|
|
@@ -151,9 +212,14 @@ class PilotCStoJSONSynchronizer:
|
|
|
151
212
|
configurationServers = gConfig.getServersList()
|
|
152
213
|
if not includeMasterCS:
|
|
153
214
|
masterCS = gConfigurationData.getMasterServer()
|
|
154
|
-
configurationServers =
|
|
215
|
+
configurationServers = exclude_master_cs_aliases(configurationServers, masterCS)
|
|
216
|
+
|
|
155
217
|
pilotDict["ConfigurationServers"] = configurationServers
|
|
156
218
|
|
|
219
|
+
preferredURLPatterns = gConfigurationData.extractOptionFromCFG("/DIRAC/PreferredURLPatterns")
|
|
220
|
+
if preferredURLPatterns:
|
|
221
|
+
pilotDict["PreferredURLPatterns"] = preferredURLPatterns
|
|
222
|
+
|
|
157
223
|
self.log.debug("Got pilotDict", str(pilotDict))
|
|
158
224
|
|
|
159
225
|
return S_OK(pilotDict)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Module holding function(s) creating the pilot wrapper.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is a DIRAC-free module, so it could possibly be used also outside of DIRAC installations.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The main client of this module is the SiteDirector, that invokes the functions here more or less like this::
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
pilotFilesCompressedEncodedDict = getPilotFilesCompressedEncodedDict(pilotFiles)
|
|
8
|
+
localPilot = pilotWrapperScript(pilotFilesCompressedEncodedDict,
|
|
9
|
+
pilotOptions,
|
|
10
|
+
pilotExecDir)
|
|
11
|
+
_writePilotWrapperFile(localPilot=localPilot)
|
|
12
12
|
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
from __future__ import absolute_import, division, print_function
|
|
15
16
|
|
|
16
17
|
import base64
|
|
@@ -18,6 +19,7 @@ import bz2
|
|
|
18
19
|
import os
|
|
19
20
|
import tempfile
|
|
20
21
|
|
|
22
|
+
|
|
21
23
|
pilotWrapperContent = """#!/bin/bash
|
|
22
24
|
# Reduce the maximum allowed number of open file descriptors as micromamba
|
|
23
25
|
# gets stuck due to https://github.com/DaanDeMeyer/reproc/pull/103
|
|
@@ -110,8 +112,26 @@ logger.addHandler(buffer)
|
|
|
110
112
|
# just logging the environment as first thing
|
|
111
113
|
logger.debug('===========================================================')
|
|
112
114
|
logger.debug('Environment of execution host\\n')
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
# Clear any non-UTF encodable environment variables as they cause the pilot to fail
|
|
116
|
+
# Use os.environb if it exists (gives raw bytes), else fall back to os.environ.
|
|
117
|
+
environb = getattr(os, "environb", os.environ)
|
|
118
|
+
for key, val in environb.items():
|
|
119
|
+
try:
|
|
120
|
+
# Decode if these are bytes; if they're already text, leave them as-is.
|
|
121
|
+
if isinstance(key, bytes):
|
|
122
|
+
key_decoded = key.decode("utf-8")
|
|
123
|
+
else:
|
|
124
|
+
key_decoded = key
|
|
125
|
+
if isinstance(val, bytes):
|
|
126
|
+
val_decoded = val.decode("utf-8")
|
|
127
|
+
else:
|
|
128
|
+
val_decoded = val
|
|
129
|
+
except UnicodeDecodeError as e:
|
|
130
|
+
logger.error("Dropping %%s=%%s due to decode error: %%s", key, val, e)
|
|
131
|
+
del environb[key]
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
logger.debug(key_decoded + '=' + val_decoded)
|
|
115
135
|
logger.debug('===========================================================\\n')
|
|
116
136
|
|
|
117
137
|
logger.debug(sys.version)
|
|
@@ -393,7 +413,17 @@ def getPilotFilesCompressedEncodedDict(pilotFiles, proxy=None):
|
|
|
393
413
|
pilotFilesCompressedEncodedDict[os.path.basename(pf)] = pfContentEncoded
|
|
394
414
|
|
|
395
415
|
if proxy is not None:
|
|
396
|
-
|
|
416
|
+
from pathlib import Path # pylint: disable=import-error
|
|
417
|
+
from DIRAC.Core.Security.ProxyFile import writeChainToTemporaryFile # pylint: disable=import-error
|
|
418
|
+
|
|
419
|
+
retVal = writeChainToTemporaryFile(proxy)
|
|
420
|
+
if not retVal["OK"]:
|
|
421
|
+
raise RuntimeError("Failed to write proxy to temporary file: %s" % retVal["Message"])
|
|
422
|
+
proxyLocation = Path(retVal["Value"])
|
|
423
|
+
proxy_string = proxyLocation.read_text()
|
|
424
|
+
proxyLocation.unlink()
|
|
425
|
+
|
|
426
|
+
compressedAndEncodedProxy = base64.b64encode(bz2.compress(proxy_string.encode()))
|
|
397
427
|
pilotFilesCompressedEncodedDict["proxy"] = compressedAndEncodedProxy
|
|
398
428
|
|
|
399
429
|
return pilotFilesCompressedEncodedDict
|
|
@@ -49,6 +49,11 @@ class RemoteRunner:
|
|
|
49
49
|
f"site {self._workloadSite}, CE {self._workloadCE}, queue {self._workloadQueue}",
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
+
# The CE interface needs to drop the token section from the proxy file to interact with the CE
|
|
53
|
+
# So we save the current proxy file location (which likely contains the DiracX token)
|
|
54
|
+
# and we will restore it at the end of the job
|
|
55
|
+
originalProxyLocation = os.environ.get("X509_USER_PROXY")
|
|
56
|
+
|
|
52
57
|
# Set up Application Queue
|
|
53
58
|
if not (result := self._setUpWorkloadCE(numberOfProcessors))["OK"]:
|
|
54
59
|
result["Errno"] = DErrno.ERESUNA
|
|
@@ -87,6 +92,8 @@ class RemoteRunner:
|
|
|
87
92
|
time.sleep(timeBetweenRetries)
|
|
88
93
|
else:
|
|
89
94
|
result["Errno"] = DErrno.EWMSSUBM
|
|
95
|
+
# Restore the original proxy location
|
|
96
|
+
os.environ["X509_USER_PROXY"] = originalProxyLocation
|
|
90
97
|
return result
|
|
91
98
|
|
|
92
99
|
jobID = result["Value"][0]
|
|
@@ -107,6 +114,8 @@ class RemoteRunner:
|
|
|
107
114
|
time.sleep(timeBetweenRetries)
|
|
108
115
|
else:
|
|
109
116
|
result["Errno"] = DErrno.EWMSSTATUS
|
|
117
|
+
# Restore the original proxy location
|
|
118
|
+
os.environ["X509_USER_PROXY"] = originalProxyLocation
|
|
110
119
|
return result
|
|
111
120
|
|
|
112
121
|
jobStatus = result["Value"][jobID]
|
|
@@ -123,6 +132,8 @@ class RemoteRunner:
|
|
|
123
132
|
time.sleep(timeBetweenRetries)
|
|
124
133
|
else:
|
|
125
134
|
result["Errno"] = DErrno.EWMSJMAN
|
|
135
|
+
# Restore the original proxy location
|
|
136
|
+
os.environ["X509_USER_PROXY"] = originalProxyLocation
|
|
126
137
|
return result
|
|
127
138
|
|
|
128
139
|
output, error = result["Value"]
|
|
@@ -131,6 +142,8 @@ class RemoteRunner:
|
|
|
131
142
|
self.log.info("Checking the integrity of the outputs...")
|
|
132
143
|
if not (result := self._checkOutputIntegrity("."))["OK"]:
|
|
133
144
|
result["Errno"] = DErrno.EWMSJMAN
|
|
145
|
+
# Restore the original proxy location
|
|
146
|
+
os.environ["X509_USER_PROXY"] = originalProxyLocation
|
|
134
147
|
return result
|
|
135
148
|
self.log.info("The output has been retrieved and declared complete")
|
|
136
149
|
|
|
@@ -146,6 +159,9 @@ class RemoteRunner:
|
|
|
146
159
|
self.log.warn("Failed to clean the output remotely", result["Message"])
|
|
147
160
|
self.log.info("The job has been remotely removed")
|
|
148
161
|
|
|
162
|
+
# Restore the original proxy location
|
|
163
|
+
os.environ["X509_USER_PROXY"] = originalProxyLocation
|
|
164
|
+
|
|
149
165
|
commandStatus = {"Done": 0, "Failed": -1, "Killed": -2}
|
|
150
166
|
return S_OK((commandStatus[jobStatus], output, error))
|
|
151
167
|
|
|
@@ -4,9 +4,12 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
|
-
from DIRAC import gLogger, S_OK
|
|
7
|
+
from DIRAC import gLogger, S_OK, S_ERROR
|
|
8
8
|
from DIRAC.Core.Utilities.File import mkDir
|
|
9
9
|
from DIRAC.FrameworkSystem.private.standardLogging.Logging import Logging
|
|
10
|
+
from DIRAC.WorkloadManagementSystem.DB.JobLoggingDB import JobLoggingDB
|
|
11
|
+
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
|
|
12
|
+
from DIRAC.WorkloadManagementSystem.DB.TaskQueueDB import TaskQueueDB
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
def createJobWrapper(
|
|
@@ -113,3 +116,35 @@ def createJobWrapper(
|
|
|
113
116
|
if rootLocation != wrapperPath:
|
|
114
117
|
generatedFiles["JobExecutableRelocatedPath"] = os.path.join(rootLocation, os.path.basename(jobExeFile))
|
|
115
118
|
return S_OK(generatedFiles)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def rescheduleJobs(jobIDs: list[int], source: str = "") -> dict:
|
|
122
|
+
"""Utility to reschedule jobs (not atomic, nor bulk)
|
|
123
|
+
Requires direct access to the JobDB and TaskQueueDB
|
|
124
|
+
|
|
125
|
+
:param jobIDs: list of jobIDs
|
|
126
|
+
:param source: source of the reschedule
|
|
127
|
+
:return: S_OK/S_ERROR
|
|
128
|
+
:rtype: dict
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
failedJobs = []
|
|
133
|
+
|
|
134
|
+
for jobID in jobIDs:
|
|
135
|
+
result = JobDB().rescheduleJob(jobID)
|
|
136
|
+
if not result["OK"]:
|
|
137
|
+
failedJobs.append(jobID)
|
|
138
|
+
continue
|
|
139
|
+
TaskQueueDB().deleteJob(jobID)
|
|
140
|
+
JobLoggingDB().addLoggingRecord(
|
|
141
|
+
result["JobID"],
|
|
142
|
+
status=result["Status"],
|
|
143
|
+
minorStatus=result["MinorStatus"],
|
|
144
|
+
applicationStatus="Unknown",
|
|
145
|
+
source=source,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if failedJobs:
|
|
149
|
+
return S_ERROR(f"Failed to reschedule jobs {failedJobs}")
|
|
150
|
+
return S_OK()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from DIRAC.WorkloadManagementSystem.Client import JobStatus
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _filterJobStateTransition(jobStates, candidateState):
|
|
5
|
+
"""Given a dictionary of jobs states,
|
|
6
|
+
return a list of jobs that are allowed to transition to the given candidate state.
|
|
7
|
+
"""
|
|
8
|
+
allowedJobs = []
|
|
9
|
+
|
|
10
|
+
for js in jobStates.items():
|
|
11
|
+
stateRes = JobStatus.JobsStateMachine(js[1]["Status"]).getNextState(candidateState)
|
|
12
|
+
if stateRes["OK"]:
|
|
13
|
+
if stateRes["Value"] == candidateState:
|
|
14
|
+
allowedJobs.append(js[0])
|
|
15
|
+
return allowedJobs
|
|
@@ -175,24 +175,10 @@ def test_logLevelValidator_invalid():
|
|
|
175
175
|
|
|
176
176
|
def test_platformValidator_valid():
|
|
177
177
|
"""Test the platform validator with valid input."""
|
|
178
|
-
|
|
179
|
-
"DIRAC.WorkloadManagementSystem.Utilities.JobModel.getDIRACPlatforms",
|
|
180
|
-
return_value=S_OK(["x86_64-slc6-gcc62-opt"]),
|
|
181
|
-
):
|
|
182
|
-
job = BaseJobDescriptionModel(executable=EXECUTABLE, platform="x86_64-slc6-gcc62-opt")
|
|
178
|
+
job = BaseJobDescriptionModel(executable=EXECUTABLE, platform="x86_64-slc6-gcc62-opt")
|
|
183
179
|
assert job.platform == "x86_64-slc6-gcc62-opt"
|
|
184
180
|
|
|
185
181
|
|
|
186
|
-
def test_platformValidator_invalid():
|
|
187
|
-
"""Test the platform validator with invalid input."""
|
|
188
|
-
with patch(
|
|
189
|
-
"DIRAC.WorkloadManagementSystem.Utilities.JobModel.getDIRACPlatforms",
|
|
190
|
-
return_value=S_OK(["x86_64-slc6-gcc62-opt"]),
|
|
191
|
-
):
|
|
192
|
-
with pytest.raises(ValidationError):
|
|
193
|
-
BaseJobDescriptionModel(executable=EXECUTABLE, platform="x86_64-slc6-gcc62-opt2")
|
|
194
|
-
|
|
195
|
-
|
|
196
182
|
@pytest.mark.parametrize(
|
|
197
183
|
"validSites, selectedSites",
|
|
198
184
|
[
|