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
|
@@ -11,8 +11,11 @@ The following options can be set in ``Systems/WorkloadManagement/Databases/JobDB
|
|
|
11
11
|
* *CompressJDLs*: Enable compression of JDLs when they are stored in the database, default *False*.
|
|
12
12
|
|
|
13
13
|
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
14
16
|
import datetime
|
|
15
17
|
import operator
|
|
18
|
+
from typing import overload
|
|
16
19
|
|
|
17
20
|
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getVOForGroup
|
|
18
21
|
from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getSiteTier
|
|
@@ -20,7 +23,14 @@ from DIRAC.Core.Base.DB import DB
|
|
|
20
23
|
from DIRAC.Core.Utilities.ClassAd.ClassAdLight import ClassAd
|
|
21
24
|
from DIRAC.Core.Utilities.Decorators import deprecated
|
|
22
25
|
from DIRAC.Core.Utilities.DErrno import EWMSJMAN, EWMSSUBM, cmpError
|
|
23
|
-
from DIRAC.Core.Utilities.ReturnValues import
|
|
26
|
+
from DIRAC.Core.Utilities.ReturnValues import (
|
|
27
|
+
S_ERROR,
|
|
28
|
+
S_OK,
|
|
29
|
+
convertToReturnValue,
|
|
30
|
+
returnValueOrRaise,
|
|
31
|
+
SErrorException,
|
|
32
|
+
DReturnType,
|
|
33
|
+
)
|
|
24
34
|
from DIRAC.FrameworkSystem.Client.Logger import contextLogger
|
|
25
35
|
from DIRAC.ResourceStatusSystem.Client.SiteStatus import SiteStatus
|
|
26
36
|
from DIRAC.WorkloadManagementSystem.Client import JobMinorStatus, JobStatus
|
|
@@ -320,23 +330,42 @@ class JobDB(DB):
|
|
|
320
330
|
|
|
321
331
|
#############################################################################
|
|
322
332
|
|
|
323
|
-
|
|
333
|
+
@overload
|
|
334
|
+
def getInputData(self, jobID: int | str) -> DReturnType[list[str]]:
|
|
335
|
+
...
|
|
336
|
+
|
|
337
|
+
@overload
|
|
338
|
+
def getInputData(self, jobID: list[int | str]) -> DReturnType[dict[int, list[str]]]:
|
|
339
|
+
...
|
|
340
|
+
|
|
341
|
+
def getInputData(self, jobID: int | str | list[int | str]) -> DReturnType[list[str] | dict[int, list[str]]]:
|
|
324
342
|
"""Get input data for the given job"""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
343
|
+
if isinstance(jobID, (int, str)):
|
|
344
|
+
ret = self._escapeString(jobID)
|
|
345
|
+
if not ret["OK"]:
|
|
346
|
+
return ret
|
|
347
|
+
jobID = ret["Value"]
|
|
348
|
+
query = f"JobID={jobID}"
|
|
349
|
+
result = []
|
|
350
|
+
else:
|
|
351
|
+
job_ids = {int(i) for i in jobID}
|
|
352
|
+
query = f"JobID IN ({','.join(map(str, job_ids))})"
|
|
353
|
+
result = {i: [] for i in job_ids}
|
|
354
|
+
cmd = f"SELECT JobID, LFN FROM InputData WHERE {query}"
|
|
330
355
|
res = self._query(cmd)
|
|
331
356
|
if not res["OK"]:
|
|
332
357
|
return res
|
|
333
358
|
|
|
334
|
-
|
|
335
|
-
|
|
359
|
+
for jid, lfn in res["Value"]:
|
|
360
|
+
lfn = lfn.strip()
|
|
336
361
|
if lfn.lower().startswith("lfn:"):
|
|
337
|
-
|
|
362
|
+
lfn = lfn[4:]
|
|
363
|
+
if isinstance(result, list):
|
|
364
|
+
result.append(lfn)
|
|
365
|
+
else:
|
|
366
|
+
result[jid].append(lfn)
|
|
338
367
|
|
|
339
|
-
return S_OK(
|
|
368
|
+
return S_OK(result)
|
|
340
369
|
|
|
341
370
|
#############################################################################
|
|
342
371
|
def setInputData(self, jobID, inputData):
|
|
@@ -1161,64 +1190,6 @@ class JobDB(DB):
|
|
|
1161
1190
|
|
|
1162
1191
|
return retVal
|
|
1163
1192
|
|
|
1164
|
-
#############################################################################
|
|
1165
|
-
def getSiteSummary(self):
|
|
1166
|
-
"""Get the summary of jobs in a given status on all the sites"""
|
|
1167
|
-
|
|
1168
|
-
waitingList = ['"Submitted"', '"Assigned"', '"Waiting"', '"Matched"']
|
|
1169
|
-
waitingString = ",".join(waitingList)
|
|
1170
|
-
|
|
1171
|
-
result = self.getDistinctJobAttributes("Site")
|
|
1172
|
-
if not result["OK"]:
|
|
1173
|
-
return result
|
|
1174
|
-
|
|
1175
|
-
siteList = result["Value"]
|
|
1176
|
-
siteDict = {}
|
|
1177
|
-
totalDict = {
|
|
1178
|
-
JobStatus.WAITING: 0,
|
|
1179
|
-
JobStatus.RUNNING: 0,
|
|
1180
|
-
JobStatus.STALLED: 0,
|
|
1181
|
-
JobStatus.DONE: 0,
|
|
1182
|
-
JobStatus.FAILED: 0,
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
for site in siteList:
|
|
1186
|
-
if site == "ANY":
|
|
1187
|
-
continue
|
|
1188
|
-
# Waiting
|
|
1189
|
-
siteDict[site] = {}
|
|
1190
|
-
ret = self._escapeString(site)
|
|
1191
|
-
if not ret["OK"]:
|
|
1192
|
-
return ret
|
|
1193
|
-
e_site = ret["Value"]
|
|
1194
|
-
|
|
1195
|
-
req = f"SELECT COUNT(JobID) FROM Jobs WHERE Status IN ({waitingString}) AND Site={e_site}"
|
|
1196
|
-
result = self._query(req)
|
|
1197
|
-
if result["OK"]:
|
|
1198
|
-
count = result["Value"][0][0]
|
|
1199
|
-
else:
|
|
1200
|
-
return S_ERROR("Failed to get Site data from the JobDB")
|
|
1201
|
-
siteDict[site][JobStatus.WAITING] = count
|
|
1202
|
-
totalDict[JobStatus.WAITING] += count
|
|
1203
|
-
# Running,Stalled,Done,Failed
|
|
1204
|
-
for status in [
|
|
1205
|
-
f'"{JobStatus.RUNNING}"',
|
|
1206
|
-
f'"{JobStatus.STALLED}"',
|
|
1207
|
-
f'"{JobStatus.DONE}"',
|
|
1208
|
-
f'"{JobStatus.FAILED}"',
|
|
1209
|
-
]:
|
|
1210
|
-
req = f"SELECT COUNT(JobID) FROM Jobs WHERE Status={status} AND Site={e_site}"
|
|
1211
|
-
result = self._query(req)
|
|
1212
|
-
if result["OK"]:
|
|
1213
|
-
count = result["Value"][0][0]
|
|
1214
|
-
else:
|
|
1215
|
-
return S_ERROR("Failed to get Site data from the JobDB")
|
|
1216
|
-
siteDict[site][status.replace('"', "")] = count
|
|
1217
|
-
totalDict[status.replace('"', "")] += count
|
|
1218
|
-
|
|
1219
|
-
siteDict["Total"] = totalDict
|
|
1220
|
-
return S_OK(siteDict)
|
|
1221
|
-
|
|
1222
1193
|
#################################################################################
|
|
1223
1194
|
def getSiteSummaryWeb(self, selectDict, sortList, startItem, maxItems):
|
|
1224
1195
|
"""Get the summary of jobs in a given status on all the sites in the standard Web form"""
|
|
@@ -1,162 +1,33 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import
|
|
3
|
+
# Import stateless functions from DIRACCommon for backward compatibility
|
|
4
|
+
from DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils import *
|
|
5
5
|
|
|
6
6
|
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
|
|
7
|
-
from DIRAC.Core.Utilities.DErrno import EWMSSUBM
|
|
8
7
|
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
|
|
9
|
-
from DIRAC.Core.Utilities.ReturnValues import
|
|
10
|
-
from DIRAC.WorkloadManagementSystem.Client import
|
|
11
|
-
from DIRAC.WorkloadManagementSystem.Client.JobState.JobManifest import JobManifest
|
|
8
|
+
from DIRAC.Core.Utilities.ReturnValues import returnValueOrRaise
|
|
9
|
+
from DIRAC.WorkloadManagementSystem.Client.JobState.JobManifest import makeJobManifestConfig
|
|
12
10
|
|
|
13
11
|
getDIRACPlatform = returnValueOrRaise(
|
|
14
12
|
ObjectLoader().loadObject("ConfigurationSystem.Client.Helpers.Resources", "getDIRACPlatform")
|
|
15
13
|
)
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
def checkAndPrepareJob(
|
|
17
|
+
jobID, classAdJob, classAdReq, owner, ownerGroup, jobAttrs, vo
|
|
18
|
+
): # pylint: disable=function-redefined
|
|
19
|
+
from DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils import checkAndPrepareJob
|
|
21
20
|
|
|
21
|
+
config = {
|
|
22
|
+
"inputDataPolicyForVO": Operations(vo=vo).getValue("InputDataPolicy/InputDataModule"),
|
|
23
|
+
"softwareDistModuleForVO": Operations(vo=vo).getValue("SoftwareDistModule"),
|
|
24
|
+
"defaultCPUTimeForOwnerGroup": Operations(group=ownerGroup).getValue("JobDescription/DefaultCPUTime", 86400),
|
|
25
|
+
"getDIRACPlatform": getDIRACPlatform,
|
|
26
|
+
}
|
|
27
|
+
return checkAndPrepareJob(jobID, classAdJob, classAdReq, owner, ownerGroup, jobAttrs, vo, config=config)
|
|
22
28
|
|
|
23
|
-
def extractJDL(compressedJDL):
|
|
24
|
-
"""Return decompressed JDL string."""
|
|
25
|
-
# the starting bracket is guaranteeed by JobManager.submitJob
|
|
26
|
-
# we need the check to be backward compatible
|
|
27
|
-
if isinstance(compressedJDL, bytes):
|
|
28
|
-
if compressedJDL.startswith(b"["):
|
|
29
|
-
return compressedJDL.decode()
|
|
30
|
-
else:
|
|
31
|
-
if compressedJDL.startswith("["):
|
|
32
|
-
return compressedJDL
|
|
33
|
-
return zlib.decompress(base64.b64decode(compressedJDL)).decode()
|
|
34
29
|
|
|
30
|
+
def checkAndAddOwner(jdl: str, owner: str, ownerGroup: str): # pylint: disable=function-redefined
|
|
31
|
+
from DIRACCommon.WorkloadManagementSystem.DB.JobDBUtils import checkAndAddOwner
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
jobManifest = JobManifest()
|
|
38
|
-
res = jobManifest.load(jdl)
|
|
39
|
-
if not res["OK"]:
|
|
40
|
-
return res
|
|
41
|
-
|
|
42
|
-
jobManifest.setOptionsFromDict({"Owner": owner, "OwnerGroup": ownerGroup})
|
|
43
|
-
res = jobManifest.check()
|
|
44
|
-
if not res["OK"]:
|
|
45
|
-
return res
|
|
46
|
-
|
|
47
|
-
return S_OK(jobManifest)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def fixJDL(jdl: str) -> str:
|
|
51
|
-
# 1.- insert original JDL on DB and get new JobID
|
|
52
|
-
# Fix the possible lack of the brackets in the JDL
|
|
53
|
-
if jdl.strip()[0].find("[") != 0:
|
|
54
|
-
jdl = "[" + jdl + "]"
|
|
55
|
-
return jdl
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def checkAndPrepareJob(jobID, classAdJob, classAdReq, owner, ownerGroup, jobAttrs, vo):
|
|
59
|
-
error = ""
|
|
60
|
-
|
|
61
|
-
jdlOwner = classAdJob.getAttributeString("Owner")
|
|
62
|
-
jdlOwnerGroup = classAdJob.getAttributeString("OwnerGroup")
|
|
63
|
-
jdlVO = classAdJob.getAttributeString("VirtualOrganization")
|
|
64
|
-
|
|
65
|
-
# The below is commented out since this is always overwritten by the submitter IDs
|
|
66
|
-
# but the check allows to findout inconsistent client environments
|
|
67
|
-
if jdlOwner and jdlOwner != owner:
|
|
68
|
-
error = "Wrong Owner in JDL"
|
|
69
|
-
elif jdlOwnerGroup and jdlOwnerGroup != ownerGroup:
|
|
70
|
-
error = "Wrong Owner Group in JDL"
|
|
71
|
-
elif jdlVO and jdlVO != vo:
|
|
72
|
-
error = "Wrong Virtual Organization in JDL"
|
|
73
|
-
|
|
74
|
-
classAdJob.insertAttributeString("Owner", owner)
|
|
75
|
-
classAdJob.insertAttributeString("OwnerGroup", ownerGroup)
|
|
76
|
-
|
|
77
|
-
if vo:
|
|
78
|
-
classAdJob.insertAttributeString("VirtualOrganization", vo)
|
|
79
|
-
|
|
80
|
-
classAdReq.insertAttributeString("Owner", owner)
|
|
81
|
-
classAdReq.insertAttributeString("OwnerGroup", ownerGroup)
|
|
82
|
-
if vo:
|
|
83
|
-
classAdReq.insertAttributeString("VirtualOrganization", vo)
|
|
84
|
-
|
|
85
|
-
inputDataPolicy = Operations(vo=vo).getValue("InputDataPolicy/InputDataModule")
|
|
86
|
-
if inputDataPolicy and not classAdJob.lookupAttribute("InputDataModule"):
|
|
87
|
-
classAdJob.insertAttributeString("InputDataModule", inputDataPolicy)
|
|
88
|
-
|
|
89
|
-
softwareDistModule = Operations(vo=vo).getValue("SoftwareDistModule")
|
|
90
|
-
if softwareDistModule and not classAdJob.lookupAttribute("SoftwareDistModule"):
|
|
91
|
-
classAdJob.insertAttributeString("SoftwareDistModule", softwareDistModule)
|
|
92
|
-
|
|
93
|
-
# priority
|
|
94
|
-
priority = classAdJob.getAttributeInt("Priority")
|
|
95
|
-
if priority is None:
|
|
96
|
-
priority = 0
|
|
97
|
-
classAdReq.insertAttributeInt("UserPriority", priority)
|
|
98
|
-
|
|
99
|
-
# CPU time
|
|
100
|
-
cpuTime = classAdJob.getAttributeInt("CPUTime")
|
|
101
|
-
if cpuTime is None:
|
|
102
|
-
opsHelper = Operations(group=ownerGroup)
|
|
103
|
-
cpuTime = opsHelper.getValue("JobDescription/DefaultCPUTime", 86400)
|
|
104
|
-
classAdReq.insertAttributeInt("CPUTime", cpuTime)
|
|
105
|
-
|
|
106
|
-
# platform(s)
|
|
107
|
-
platformList = classAdJob.getListFromExpression("Platform")
|
|
108
|
-
if platformList:
|
|
109
|
-
result = getDIRACPlatform(platformList)
|
|
110
|
-
if not result["OK"]:
|
|
111
|
-
return result
|
|
112
|
-
if result["Value"]:
|
|
113
|
-
classAdReq.insertAttributeVectorString("Platforms", result["Value"])
|
|
114
|
-
else:
|
|
115
|
-
error = "OS compatibility info not found"
|
|
116
|
-
if error:
|
|
117
|
-
retVal = S_ERROR(EWMSSUBM, error)
|
|
118
|
-
retVal["JobId"] = jobID
|
|
119
|
-
retVal["Status"] = JobStatus.FAILED
|
|
120
|
-
retVal["MinorStatus"] = error
|
|
121
|
-
|
|
122
|
-
jobAttrs["Status"] = JobStatus.FAILED
|
|
123
|
-
|
|
124
|
-
jobAttrs["MinorStatus"] = error
|
|
125
|
-
return retVal
|
|
126
|
-
return S_OK()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def createJDLWithInitialStatus(
|
|
130
|
-
classAdJob, classAdReq, jdl2DBParameters, jobAttrs, initialStatus, initialMinorStatus, *, modern=False
|
|
131
|
-
):
|
|
132
|
-
"""
|
|
133
|
-
:param modern: if True, store boolean instead of string for VerifiedFlag (used by diracx only)
|
|
134
|
-
"""
|
|
135
|
-
priority = classAdJob.getAttributeInt("Priority")
|
|
136
|
-
if priority is None:
|
|
137
|
-
priority = 0
|
|
138
|
-
jobAttrs["UserPriority"] = priority
|
|
139
|
-
|
|
140
|
-
for jdlName in jdl2DBParameters:
|
|
141
|
-
# Defaults are set by the DB.
|
|
142
|
-
jdlValue = classAdJob.getAttributeString(jdlName)
|
|
143
|
-
if jdlValue:
|
|
144
|
-
jobAttrs[jdlName] = jdlValue
|
|
145
|
-
|
|
146
|
-
jdlValue = classAdJob.getAttributeString("Site")
|
|
147
|
-
if jdlValue:
|
|
148
|
-
if jdlValue.find(",") != -1:
|
|
149
|
-
jobAttrs["Site"] = "Multiple"
|
|
150
|
-
else:
|
|
151
|
-
jobAttrs["Site"] = jdlValue
|
|
152
|
-
|
|
153
|
-
jobAttrs["VerifiedFlag"] = True if modern else "True"
|
|
154
|
-
|
|
155
|
-
jobAttrs["Status"] = initialStatus
|
|
156
|
-
|
|
157
|
-
jobAttrs["MinorStatus"] = initialMinorStatus
|
|
158
|
-
|
|
159
|
-
reqJDL = classAdReq.asJDL()
|
|
160
|
-
classAdJob.insertAttributeInt("JobRequirements", reqJDL)
|
|
161
|
-
|
|
162
|
-
return classAdJob.asJDL()
|
|
33
|
+
return checkAndAddOwner(jdl, owner, ownerGroup, job_manifest_config=makeJobManifestConfig(ownerGroup))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""Module containing a front-end to the OpenSearch-based JobParametersDB.
|
|
2
|
+
This is a drop-in replacement for MySQL-based table JobDB.JobParameters.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
The following class methods are provided for public usage
|
|
5
|
+
- getJobParameters()
|
|
6
|
+
- setJobParameter()
|
|
7
|
+
- deleteJobParameters()
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from DIRAC import S_ERROR, S_OK
|
|
@@ -37,11 +37,11 @@ class JobParametersDB(ElasticDB):
|
|
|
37
37
|
def __init__(self, parentLogger=None):
|
|
38
38
|
"""Standard Constructor"""
|
|
39
39
|
|
|
40
|
-
self.fullname = "WorkloadManagement/
|
|
40
|
+
self.fullname = "WorkloadManagement/JobParametersDB"
|
|
41
41
|
self.index_name = self.getCSOption("index_name", "job_parameters")
|
|
42
42
|
|
|
43
43
|
try:
|
|
44
|
-
# Connecting to the
|
|
44
|
+
# Connecting to the OpenSearch cluster
|
|
45
45
|
super().__init__(self.fullname, self.index_name, parentLogger=parentLogger)
|
|
46
46
|
except Exception:
|
|
47
47
|
RuntimeError("Can't connect to JobParameters index")
|
|
@@ -69,7 +69,7 @@ class JobParametersDB(ElasticDB):
|
|
|
69
69
|
raise RuntimeError(result["Message"])
|
|
70
70
|
self.log.always("Index created:", indexName)
|
|
71
71
|
|
|
72
|
-
def getJobParameters(self, jobIDs: int | list[int], vo: str, paramList=None) -> dict:
|
|
72
|
+
def getJobParameters(self, jobIDs: int | list[int], vo: str, paramList: list[str] | None = None) -> dict:
|
|
73
73
|
"""Get Job Parameters defined for jobID.
|
|
74
74
|
Returns a dictionary with the Job Parameters.
|
|
75
75
|
If paramList is empty - all the parameters are returned.
|
|
@@ -447,9 +447,10 @@ AND SubmissionTime < DATE_SUB(UTC_TIMESTAMP(),INTERVAL %d DAY)"
|
|
|
447
447
|
"""Get IDs of Jobs that were executed by a pilot"""
|
|
448
448
|
cmd = "SELECT pilotID,JobID FROM JobToPilotMapping "
|
|
449
449
|
if isinstance(pilotID, list):
|
|
450
|
-
|
|
450
|
+
pilotIDs_string = ",".join(str(int(x)) for x in pilotID)
|
|
451
|
+
cmd = f"{cmd} WHERE pilotID IN ({pilotIDs_string})"
|
|
451
452
|
else:
|
|
452
|
-
cmd =
|
|
453
|
+
cmd = f"{cmd} WHERE pilotID = {pilotID}"
|
|
453
454
|
|
|
454
455
|
result = self._query(cmd)
|
|
455
456
|
if not result["OK"]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
""" SandboxMetadataDB class is a front-end to the metadata for sandboxes
|
|
2
2
|
"""
|
|
3
|
-
from DIRAC import S_ERROR, S_OK
|
|
3
|
+
from DIRAC import S_ERROR, S_OK
|
|
4
4
|
from DIRAC.ConfigurationSystem.Client.Helpers import Registry
|
|
5
5
|
from DIRAC.Core.Base.DB import DB
|
|
6
6
|
from DIRAC.Core.Security import Properties
|
|
@@ -64,7 +64,7 @@ class SandboxMetadataDB(DB):
|
|
|
64
64
|
"Type": "VARCHAR(64) NOT NULL",
|
|
65
65
|
},
|
|
66
66
|
"Indexes": {"Entity": ["EntityId"], "SBIndex": ["SBId"]},
|
|
67
|
-
"
|
|
67
|
+
"PrimaryKey": ["SBId", "EntityId", "Type"],
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
for tableName in self.__tablesDesc:
|
|
@@ -220,48 +220,22 @@ class SandboxMetadataDB(DB):
|
|
|
220
220
|
return result
|
|
221
221
|
return S_OK(assigned)
|
|
222
222
|
|
|
223
|
-
def __entitiesByRequesterCond(self, requesterName, requesterGroup):
|
|
224
|
-
sqlCond = []
|
|
225
|
-
requesterProps = Registry.getPropertiesForEntity(requesterGroup, name=requesterName)
|
|
226
|
-
if Properties.JOB_ADMINISTRATOR in requesterProps:
|
|
227
|
-
# Do nothing, just ensure it doesn't fit in the other cases
|
|
228
|
-
pass
|
|
229
|
-
elif Properties.JOB_SHARING in requesterProps:
|
|
230
|
-
sqlCond.append(f"o.OwnerGroup='{requesterGroup}'")
|
|
231
|
-
elif Properties.NORMAL_USER in requesterProps:
|
|
232
|
-
sqlCond.append(f"o.OwnerGroup='{requesterGroup}'")
|
|
233
|
-
sqlCond.append(f"o.Owner='{requesterName}'")
|
|
234
|
-
else:
|
|
235
|
-
return S_ERROR("Not authorized to access sandbox")
|
|
236
|
-
return sqlCond
|
|
237
|
-
|
|
238
223
|
@convertToReturnValue
|
|
239
|
-
def unassignEntities(self, entities
|
|
224
|
+
def unassignEntities(self, entities: list):
|
|
240
225
|
"""
|
|
241
|
-
Unassign
|
|
226
|
+
Unassign entities to sandboxes. Entities are a list of strings, e.g. ['job:1234', 'job:5678'].
|
|
242
227
|
|
|
243
228
|
:param list entities: list of entities to unassign
|
|
244
229
|
"""
|
|
245
230
|
if not entities:
|
|
246
231
|
return None
|
|
247
|
-
conds = self.__entitiesByRequesterCond(requesterName, requesterGroup)
|
|
248
232
|
|
|
249
|
-
sqlCmd = "CREATE TEMPORARY TABLE to_delete_EntityId (EntityId VARCHAR(128)
|
|
233
|
+
sqlCmd = "CREATE TEMPORARY TABLE to_delete_EntityId (EntityId VARCHAR(128) NOT NULL, PRIMARY KEY (EntityId)) ENGINE=MEMORY;"
|
|
250
234
|
returnValueOrRaise(self._update(sqlCmd))
|
|
251
235
|
try:
|
|
252
236
|
sqlCmd = "INSERT INTO to_delete_EntityId (EntityId) VALUES ( %s )"
|
|
253
237
|
returnValueOrRaise(self._updatemany(sqlCmd, [(e,) for e in entities]))
|
|
254
238
|
sqlCmd = "DELETE m from `sb_EntityMapping` m JOIN to_delete_EntityId t USING (EntityId)"
|
|
255
|
-
if conds:
|
|
256
|
-
sqlCmd = " ".join(
|
|
257
|
-
[
|
|
258
|
-
sqlCmd,
|
|
259
|
-
"JOIN `sb_SandBoxes` s ON s.SBId = m.SBId",
|
|
260
|
-
"JOIN `sb_Owners` o ON s.OwnerId = o.OwnerId",
|
|
261
|
-
"WHERE",
|
|
262
|
-
" AND ".join(conds),
|
|
263
|
-
]
|
|
264
|
-
)
|
|
265
239
|
returnValueOrRaise(self._update(sqlCmd))
|
|
266
240
|
finally:
|
|
267
241
|
sqlCmd = "DROP TEMPORARY TABLE to_delete_EntityId"
|
|
@@ -309,19 +283,34 @@ class SandboxMetadataDB(DB):
|
|
|
309
283
|
"TIMESTAMPDIFF( DAY, LastAccessTime, UTC_TIMESTAMP() ) >= %d" % self.__assignedSBGraceDays,
|
|
310
284
|
f"! Assigned AND TIMESTAMPDIFF( DAY, LastAccessTime, UTC_TIMESTAMP() ) >= {self.__unassignedSBGraceDays}",
|
|
311
285
|
]
|
|
312
|
-
|
|
286
|
+
# Exclude sandboxes that are in S3 as those are handled by DiracX
|
|
287
|
+
sqlCmd = f"SELECT SBId, SEName, SEPFN FROM `sb_SandBoxes` WHERE SEPFN not like '/S3/%' AND (( {' ) OR ( '.join(sqlCond)} ))"
|
|
313
288
|
return self._query(sqlCmd)
|
|
314
289
|
|
|
290
|
+
@convertToReturnValue
|
|
315
291
|
def deleteSandboxes(self, SBIdList):
|
|
316
292
|
"""
|
|
317
|
-
Delete sandboxes
|
|
293
|
+
Delete sandboxes using a temporary table for efficiency and consistency.
|
|
318
294
|
"""
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
295
|
+
if not SBIdList:
|
|
296
|
+
return S_OK()
|
|
297
|
+
# Create temporary table
|
|
298
|
+
sqlCmd = "CREATE TEMPORARY TABLE to_delete_SBId (SBId INTEGER(10) UNSIGNED NOT NULL, PRIMARY KEY (SBId)) ENGINE=MEMORY;"
|
|
299
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
300
|
+
try:
|
|
301
|
+
# Insert SBIds into temporary table
|
|
302
|
+
sqlCmd = "INSERT INTO to_delete_SBId (SBId) VALUES (%s)"
|
|
303
|
+
returnValueOrRaise(self._updatemany(sqlCmd, [(sbid,) for sbid in SBIdList]))
|
|
304
|
+
# Delete from sb_EntityMapping first (to respect FK constraints if any)
|
|
305
|
+
sqlCmd = "DELETE FROM `sb_EntityMapping` WHERE SBId IN (SELECT SBId FROM to_delete_SBId)"
|
|
306
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
307
|
+
# Delete from sb_SandBoxes
|
|
308
|
+
sqlCmd = "DELETE FROM `sb_SandBoxes` WHERE SBId IN (SELECT SBId FROM to_delete_SBId)"
|
|
309
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
310
|
+
finally:
|
|
311
|
+
# Drop temporary table
|
|
312
|
+
sqlCmd = "DROP TEMPORARY TABLE to_delete_SBId"
|
|
313
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
325
314
|
return S_OK()
|
|
326
315
|
|
|
327
316
|
def getSandboxId(self, SEName, SEPFN, requesterName, requesterGroup, field="SBId"):
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from DIRAC import S_ERROR, S_OK, gLogger
|
|
2
|
+
from DIRAC.StorageManagementSystem.DB.StorageManagementDB import StorageManagementDB
|
|
3
|
+
from DIRAC.WorkloadManagementSystem.Client import JobStatus
|
|
4
|
+
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
|
|
5
|
+
from DIRAC.WorkloadManagementSystem.DB.PilotAgentsDB import PilotAgentsDB
|
|
6
|
+
from DIRAC.WorkloadManagementSystem.DB.TaskQueueDB import TaskQueueDB
|
|
7
|
+
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_DELETE, RIGHT_KILL
|
|
8
|
+
from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import _filterJobStateTransition
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _deleteJob(jobID, force=False):
|
|
12
|
+
"""Set the job status to "Deleted"
|
|
13
|
+
and remove the pilot that ran and its logging info if the pilot is finished.
|
|
14
|
+
|
|
15
|
+
:param int jobID: job ID
|
|
16
|
+
:return: S_OK()/S_ERROR()
|
|
17
|
+
"""
|
|
18
|
+
if not (result := JobDB().setJobStatus(jobID, JobStatus.DELETED, "Checking accounting", force=force))["OK"]:
|
|
19
|
+
gLogger.warn("Failed to set job Deleted status", result["Message"])
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
|
|
23
|
+
gLogger.warn("Failed to delete job from the TaskQueue")
|
|
24
|
+
|
|
25
|
+
# if it was the last job for the pilot
|
|
26
|
+
result = PilotAgentsDB().getPilotsForJobID(jobID)
|
|
27
|
+
if not result["OK"]:
|
|
28
|
+
gLogger.error("Failed to get Pilots for JobID", result["Message"])
|
|
29
|
+
return result
|
|
30
|
+
for pilot in result["Value"]:
|
|
31
|
+
res = PilotAgentsDB().getJobsForPilot(pilot)
|
|
32
|
+
if not res["OK"]:
|
|
33
|
+
gLogger.error("Failed to get jobs for pilot", res["Message"])
|
|
34
|
+
return res
|
|
35
|
+
if not res["Value"]: # if list of jobs for pilot is empty, delete pilot
|
|
36
|
+
result = PilotAgentsDB().getPilotInfo(pilotID=pilot)
|
|
37
|
+
if not result["OK"]:
|
|
38
|
+
gLogger.error("Failed to get pilot info", result["Message"])
|
|
39
|
+
return result
|
|
40
|
+
ret = PilotAgentsDB().deletePilot(result["Value"]["PilotJobReference"])
|
|
41
|
+
if not ret["OK"]:
|
|
42
|
+
gLogger.error("Failed to delete pilot from PilotAgentsDB", ret["Message"])
|
|
43
|
+
return ret
|
|
44
|
+
|
|
45
|
+
return S_OK()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _killJob(jobID, sendKillCommand=True, force=False):
|
|
49
|
+
"""Kill one job
|
|
50
|
+
|
|
51
|
+
:param int jobID: job ID
|
|
52
|
+
:param bool sendKillCommand: send kill command
|
|
53
|
+
|
|
54
|
+
:return: S_OK()/S_ERROR()
|
|
55
|
+
"""
|
|
56
|
+
if sendKillCommand:
|
|
57
|
+
if not (result := JobDB().setJobCommand(jobID, "Kill"))["OK"]:
|
|
58
|
+
gLogger.warn("Failed to set job Kill command", result["Message"])
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
gLogger.info("Job marked for termination", jobID)
|
|
62
|
+
if not (result := JobDB().setJobStatus(jobID, JobStatus.KILLED, "Marked for termination", force=force))["OK"]:
|
|
63
|
+
gLogger.warn("Failed to set job Killed status", result["Message"])
|
|
64
|
+
if not (result := TaskQueueDB().deleteJob(jobID))["OK"]:
|
|
65
|
+
gLogger.warn("Failed to delete job from the TaskQueue", result["Message"])
|
|
66
|
+
|
|
67
|
+
return S_OK()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def kill_delete_jobs(right, validJobList, nonauthJobList=[], force=False):
|
|
71
|
+
"""Kill (== set the status to "KILLED") or delete (== set the status to "DELETED") jobs as necessary
|
|
72
|
+
|
|
73
|
+
:param str right: RIGHT_KILL or RIGHT_DELETE
|
|
74
|
+
|
|
75
|
+
:return: S_OK()/S_ERROR()
|
|
76
|
+
"""
|
|
77
|
+
badIDs = []
|
|
78
|
+
|
|
79
|
+
killJobList = []
|
|
80
|
+
deleteJobList = []
|
|
81
|
+
if validJobList:
|
|
82
|
+
result = JobDB().getJobsAttributes(validJobList, ["Status"])
|
|
83
|
+
if not result["OK"]:
|
|
84
|
+
return result
|
|
85
|
+
jobStates = result["Value"]
|
|
86
|
+
|
|
87
|
+
# Get the jobs allowed to transition to the Killed state
|
|
88
|
+
killJobList.extend(_filterJobStateTransition(jobStates, JobStatus.KILLED))
|
|
89
|
+
|
|
90
|
+
if right == RIGHT_DELETE:
|
|
91
|
+
# Get the jobs allowed to transition to the Deleted state
|
|
92
|
+
deleteJobList.extend(_filterJobStateTransition(jobStates, JobStatus.DELETED))
|
|
93
|
+
|
|
94
|
+
for jobID in killJobList:
|
|
95
|
+
result = _killJob(jobID, force=force)
|
|
96
|
+
if not result["OK"]:
|
|
97
|
+
badIDs.append(jobID)
|
|
98
|
+
|
|
99
|
+
for jobID in deleteJobList:
|
|
100
|
+
result = _deleteJob(jobID, force=force)
|
|
101
|
+
if not result["OK"]:
|
|
102
|
+
badIDs.append(jobID)
|
|
103
|
+
|
|
104
|
+
# Look for jobs that are in the Staging state to send kill signal to the stager
|
|
105
|
+
stagingJobList = [jobID for jobID, sDict in jobStates.items() if sDict["Status"] == JobStatus.STAGING]
|
|
106
|
+
|
|
107
|
+
if stagingJobList:
|
|
108
|
+
stagerDB = StorageManagementDB()
|
|
109
|
+
gLogger.info("Going to send killing signal to stager as well!")
|
|
110
|
+
result = stagerDB.killTasksBySourceTaskID(stagingJobList)
|
|
111
|
+
if not result["OK"]:
|
|
112
|
+
gLogger.warn("Failed to kill some Stager tasks", result["Message"])
|
|
113
|
+
|
|
114
|
+
if nonauthJobList or badIDs:
|
|
115
|
+
result = S_ERROR("Some jobs failed deletion")
|
|
116
|
+
if nonauthJobList:
|
|
117
|
+
gLogger.warn("Non-authorized JobIDs won't be deleted", str(nonauthJobList))
|
|
118
|
+
result["NonauthorizedJobIDs"] = nonauthJobList
|
|
119
|
+
if badIDs:
|
|
120
|
+
gLogger.warn("JobIDs failed to be deleted", str(badIDs))
|
|
121
|
+
result["FailedJobIDs"] = badIDs
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
jobsList = killJobList if right == RIGHT_KILL else deleteJobList
|
|
125
|
+
return S_OK(jobsList)
|
|
@@ -28,7 +28,7 @@ def test_getInputData(jobDB: JobDB):
|
|
|
28
28
|
"""Test the getInputData method from JobDB"""
|
|
29
29
|
# Arrange
|
|
30
30
|
jobDB._escapeString = MagicMock(return_value=S_OK())
|
|
31
|
-
jobDB._query = MagicMock(return_value=S_OK((
|
|
31
|
+
jobDB._query = MagicMock(return_value=S_OK([(1234, "/vo/user/lfn1"), (1234, "LFN:/vo/user/lfn2")]))
|
|
32
32
|
|
|
33
33
|
# Act
|
|
34
34
|
res = jobDB.getInputData(1234)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
""" unit test (pytest) of JobAdministration module
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from unittest.mock import MagicMock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
# sut
|
|
9
|
+
from DIRAC.WorkloadManagementSystem.DB.StatusUtils import kill_delete_jobs
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize(
|
|
13
|
+
"jobIDs_list, right",
|
|
14
|
+
[
|
|
15
|
+
([], "Kill"),
|
|
16
|
+
([], "Delete"),
|
|
17
|
+
(1, "Kill"),
|
|
18
|
+
([1, 2], "Kill"),
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
def test___kill_delete_jobs(mocker, jobIDs_list, right):
|
|
22
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.JobDB", MagicMock())
|
|
23
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.TaskQueueDB", MagicMock())
|
|
24
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.PilotAgentsDB", MagicMock())
|
|
25
|
+
mocker.patch("DIRAC.WorkloadManagementSystem.DB.StatusUtils.StorageManagementDB", MagicMock())
|
|
26
|
+
|
|
27
|
+
res = kill_delete_jobs(right, jobIDs_list)
|
|
28
|
+
assert res["OK"]
|