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
|
@@ -28,6 +28,7 @@ from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getCESiteMapping
|
|
|
28
28
|
from DIRAC.Core.Base.DB import DB
|
|
29
29
|
from DIRAC.Core.Utilities import DErrno
|
|
30
30
|
from DIRAC.Core.Utilities.MySQL import _quotedList
|
|
31
|
+
from DIRAC.Core.Utilities.ReturnValues import returnValueOrRaise
|
|
31
32
|
from DIRAC.FrameworkSystem.Client.Logger import contextLogger
|
|
32
33
|
from DIRAC.ResourceStatusSystem.Client.SiteStatus import SiteStatus
|
|
33
34
|
from DIRAC.WorkloadManagementSystem.Client import PilotStatus
|
|
@@ -401,9 +402,18 @@ AND SubmissionTime < DATE_SUB(UTC_TIMESTAMP(),INTERVAL %d DAY)"
|
|
|
401
402
|
if result["Value"]:
|
|
402
403
|
return int(result["Value"][0][0])
|
|
403
404
|
return 0
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
405
|
+
|
|
406
|
+
sqlCmd = "CREATE TEMPORARY TABLE to_select_PilotAgents (PilotID VARCHAR(255) NOT NULL, PRIMARY KEY (PilotID)) ENGINE=MEMORY;"
|
|
407
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
408
|
+
try:
|
|
409
|
+
sqlCmd = "INSERT INTO to_select_PilotAgents (PilotID) VALUES ( %s )"
|
|
410
|
+
returnValueOrRaise(self._updatemany(sqlCmd, [(p,) for p in pilotRef]))
|
|
411
|
+
sqlCmd = "SELECT PilotID FROM PilotAgents JOIN to_select_PilotAgents USING (PilotID)"
|
|
412
|
+
result = self._query(sqlCmd)
|
|
413
|
+
finally:
|
|
414
|
+
sqlCmd = "DROP TEMPORARY TABLE to_select_PilotAgents"
|
|
415
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
416
|
+
|
|
407
417
|
if not result["OK"]:
|
|
408
418
|
return []
|
|
409
419
|
if result["Value"]:
|
|
@@ -437,9 +447,10 @@ AND SubmissionTime < DATE_SUB(UTC_TIMESTAMP(),INTERVAL %d DAY)"
|
|
|
437
447
|
"""Get IDs of Jobs that were executed by a pilot"""
|
|
438
448
|
cmd = "SELECT pilotID,JobID FROM JobToPilotMapping "
|
|
439
449
|
if isinstance(pilotID, list):
|
|
440
|
-
|
|
450
|
+
pilotIDs_string = ",".join(str(int(x)) for x in pilotID)
|
|
451
|
+
cmd = f"{cmd} WHERE pilotID IN ({pilotIDs_string})"
|
|
441
452
|
else:
|
|
442
|
-
cmd =
|
|
453
|
+
cmd = f"{cmd} WHERE pilotID = {pilotID}"
|
|
443
454
|
|
|
444
455
|
result = self._query(cmd)
|
|
445
456
|
if not result["OK"]:
|
|
@@ -46,7 +46,7 @@ CREATE TABLE `PilotAgents` (
|
|
|
46
46
|
KEY `PilotJobReference` (`PilotJobReference`),
|
|
47
47
|
KEY `Status` (`Status`),
|
|
48
48
|
KEY `Statuskey` (`GridSite`,`DestinationSite`,`Status`)
|
|
49
|
-
) ENGINE=InnoDB DEFAULT CHARSET=
|
|
49
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
DROP TABLE IF EXISTS `JobToPilotMapping`;
|
|
@@ -56,7 +56,7 @@ CREATE TABLE `JobToPilotMapping` (
|
|
|
56
56
|
`StartTime` DATETIME NOT NULL,
|
|
57
57
|
KEY `JobID` (`JobID`),
|
|
58
58
|
KEY `PilotID` (`PilotID`)
|
|
59
|
-
) ENGINE=InnoDB DEFAULT CHARSET=
|
|
59
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
60
60
|
|
|
61
61
|
DROP TABLE IF EXISTS `PilotOutput`;
|
|
62
62
|
CREATE TABLE `PilotOutput` (
|
|
@@ -64,4 +64,4 @@ CREATE TABLE `PilotOutput` (
|
|
|
64
64
|
`StdOutput` MEDIUMTEXT,
|
|
65
65
|
`StdError` MEDIUMTEXT,
|
|
66
66
|
PRIMARY KEY (`PilotID`)
|
|
67
|
-
) ENGINE=InnoDB DEFAULT CHARSET=
|
|
67
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@@ -1,10 +1,11 @@
|
|
|
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
|
|
7
7
|
from DIRAC.Core.Utilities import List
|
|
8
|
+
from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue, returnValueOrRaise
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class SandboxMetadataDB(DB):
|
|
@@ -63,7 +64,7 @@ class SandboxMetadataDB(DB):
|
|
|
63
64
|
"Type": "VARCHAR(64) NOT NULL",
|
|
64
65
|
},
|
|
65
66
|
"Indexes": {"Entity": ["EntityId"], "SBIndex": ["SBId"]},
|
|
66
|
-
"
|
|
67
|
+
"PrimaryKey": ["SBId", "EntityId", "Type"],
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
for tableName in self.__tablesDesc:
|
|
@@ -219,60 +220,27 @@ class SandboxMetadataDB(DB):
|
|
|
219
220
|
return result
|
|
220
221
|
return S_OK(assigned)
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
@convertToReturnValue
|
|
224
|
+
def unassignEntities(self, entities: list):
|
|
223
225
|
"""
|
|
224
|
-
|
|
225
|
-
"""
|
|
226
|
-
sqlCond = ["s.OwnerId=o.OwnerId", "s.SBId=e.SBId"]
|
|
227
|
-
requesterProps = Registry.getPropertiesForEntity(requesterGroup, name=requesterName)
|
|
228
|
-
if Properties.JOB_ADMINISTRATOR in requesterProps:
|
|
229
|
-
# Do nothing, just ensure it doesn't fit in the other cases
|
|
230
|
-
pass
|
|
231
|
-
elif Properties.JOB_SHARING in requesterProps:
|
|
232
|
-
sqlCond.append(f"o.OwnerGroup='{requesterGroup}'")
|
|
233
|
-
elif Properties.NORMAL_USER in requesterProps:
|
|
234
|
-
sqlCond.append(f"o.OwnerGroup='{requesterGroup}'")
|
|
235
|
-
sqlCond.append(f"o.Owner='{requesterName}'")
|
|
236
|
-
else:
|
|
237
|
-
return S_ERROR("Not authorized to access sandbox")
|
|
238
|
-
for i in range(len(entitiesList)):
|
|
239
|
-
entitiesList[i] = self._escapeString(entitiesList[i])["Value"]
|
|
240
|
-
if len(entitiesList) == 1:
|
|
241
|
-
sqlCond.append(f"e.EntityId = {entitiesList[0]}")
|
|
242
|
-
else:
|
|
243
|
-
sqlCond.append(f"e.EntityId in ( {', '.join(entitiesList)} )")
|
|
244
|
-
sqlCmd = "SELECT DISTINCT e.EntityId FROM `sb_EntityMapping` e, `sb_SandBoxes` s, `sb_Owners` o WHERE"
|
|
245
|
-
sqlCmd = f"{sqlCmd} {' AND '.join(sqlCond)}"
|
|
246
|
-
result = self._query(sqlCmd)
|
|
247
|
-
if not result["OK"]:
|
|
248
|
-
return result
|
|
249
|
-
return S_OK([row[0] for row in result["Value"]])
|
|
250
|
-
|
|
251
|
-
def unassignEntities(self, entities, requesterName, requesterGroup):
|
|
252
|
-
"""
|
|
253
|
-
Unassign jobs to sandboxes
|
|
226
|
+
Unassign entities to sandboxes. Entities are a list of strings, e.g. ['job:1234', 'job:5678'].
|
|
254
227
|
|
|
255
228
|
:param list entities: list of entities to unassign
|
|
256
229
|
"""
|
|
257
|
-
updated = 0
|
|
258
230
|
if not entities:
|
|
259
|
-
return
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
gLogger.error("Cannot unassign entities", result["Message"])
|
|
273
|
-
else:
|
|
274
|
-
updated += 1
|
|
275
|
-
return S_OK(updated)
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
sqlCmd = "CREATE TEMPORARY TABLE to_delete_EntityId (EntityId VARCHAR(128) NOT NULL, PRIMARY KEY (EntityId)) ENGINE=MEMORY;"
|
|
234
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
235
|
+
try:
|
|
236
|
+
sqlCmd = "INSERT INTO to_delete_EntityId (EntityId) VALUES ( %s )"
|
|
237
|
+
returnValueOrRaise(self._updatemany(sqlCmd, [(e,) for e in entities]))
|
|
238
|
+
sqlCmd = "DELETE m from `sb_EntityMapping` m JOIN to_delete_EntityId t USING (EntityId)"
|
|
239
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
240
|
+
finally:
|
|
241
|
+
sqlCmd = "DROP TEMPORARY TABLE to_delete_EntityId"
|
|
242
|
+
returnValueOrRaise(self._update(sqlCmd))
|
|
243
|
+
return 1
|
|
276
244
|
|
|
277
245
|
def getSandboxesAssignedToEntity(self, entityId, requesterName, requesterGroup, requestedVO):
|
|
278
246
|
"""
|
|
@@ -315,22 +283,37 @@ class SandboxMetadataDB(DB):
|
|
|
315
283
|
"TIMESTAMPDIFF( DAY, LastAccessTime, UTC_TIMESTAMP() ) >= %d" % self.__assignedSBGraceDays,
|
|
316
284
|
f"! Assigned AND TIMESTAMPDIFF( DAY, LastAccessTime, UTC_TIMESTAMP() ) >= {self.__unassignedSBGraceDays}",
|
|
317
285
|
]
|
|
318
|
-
|
|
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)} ))"
|
|
319
288
|
return self._query(sqlCmd)
|
|
320
289
|
|
|
290
|
+
@convertToReturnValue
|
|
321
291
|
def deleteSandboxes(self, SBIdList):
|
|
322
292
|
"""
|
|
323
|
-
Delete sandboxes
|
|
293
|
+
Delete sandboxes using a temporary table for efficiency and consistency.
|
|
324
294
|
"""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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))
|
|
331
314
|
return S_OK()
|
|
332
315
|
|
|
333
|
-
def getSandboxId(self, SEName, SEPFN, requesterName, requesterGroup, field="SBId"
|
|
316
|
+
def getSandboxId(self, SEName, SEPFN, requesterName, requesterGroup, field="SBId"):
|
|
334
317
|
"""
|
|
335
318
|
Get the sandboxId if it exists
|
|
336
319
|
|
|
@@ -350,7 +333,7 @@ class SandboxMetadataDB(DB):
|
|
|
350
333
|
"s.OwnerId=o.OwnerId",
|
|
351
334
|
]
|
|
352
335
|
sqlCmd = f"SELECT s.{field} FROM `sb_SandBoxes` s, `sb_Owners` o WHERE"
|
|
353
|
-
requesterProps = Registry.getPropertiesForEntity(requesterGroup, name=requesterName
|
|
336
|
+
requesterProps = Registry.getPropertiesForEntity(requesterGroup, name=requesterName)
|
|
354
337
|
if Properties.JOB_ADMINISTRATOR in requesterProps or Properties.JOB_MONITOR in requesterProps:
|
|
355
338
|
# Do nothing, just ensure it doesn't fit in the other cases
|
|
356
339
|
pass
|
|
@@ -370,24 +353,3 @@ class SandboxMetadataDB(DB):
|
|
|
370
353
|
if not data:
|
|
371
354
|
return S_ERROR("No sandbox matches the requirements")
|
|
372
355
|
return S_OK(data[0][0])
|
|
373
|
-
|
|
374
|
-
def getSandboxOwner(self, SEName, SEPFN, requesterDN, requesterGroup):
|
|
375
|
-
"""get the sandbox owner, if such sandbox exists
|
|
376
|
-
|
|
377
|
-
:param SEName: name of the StorageElement
|
|
378
|
-
:param SEPFN: PFN of the Sandbox
|
|
379
|
-
:param requesterDN: host DN used as credentials
|
|
380
|
-
:param requesterGroup: group used to use as credentials (should be 'hosts')
|
|
381
|
-
|
|
382
|
-
:returns: S_OK with tuple (owner, ownerGroup, VO)
|
|
383
|
-
"""
|
|
384
|
-
if not (res := self.getSandboxId(SEName, SEPFN, None, requesterGroup, "OwnerId", requesterDN=requesterDN))[
|
|
385
|
-
"OK"
|
|
386
|
-
]:
|
|
387
|
-
return res
|
|
388
|
-
|
|
389
|
-
if not (
|
|
390
|
-
res := self._query(f"SELECT `Owner`, `OwnerGroup`, `VO` FROM `sb_Owners` WHERE `OwnerId` = {res['Value']}")
|
|
391
|
-
)["OK"]:
|
|
392
|
-
return res
|
|
393
|
-
return S_OK(res["Value"][0])
|
|
@@ -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"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
""" The Job Sanity executor assigns sandboxes to the job """
|
|
2
2
|
from DIRAC import S_OK
|
|
3
|
-
from DIRAC.WorkloadManagementSystem.
|
|
3
|
+
from DIRAC.WorkloadManagementSystem.DB.SandboxMetadataDB import SandboxMetadataDB
|
|
4
4
|
from DIRAC.WorkloadManagementSystem.Executor.Base.OptimizerExecutor import OptimizerExecutor
|
|
5
5
|
|
|
6
6
|
|
|
@@ -15,7 +15,6 @@ class JobSanity(OptimizerExecutor):
|
|
|
15
15
|
@classmethod
|
|
16
16
|
def initializeOptimizer(cls):
|
|
17
17
|
"""Initialize specific parameters for JobSanityAgent."""
|
|
18
|
-
cls.sandboxClient = SandboxStoreClient(useCertificates=True, smdb=True)
|
|
19
18
|
return S_OK()
|
|
20
19
|
|
|
21
20
|
def optimizeJob(self, jid, jobState):
|
|
@@ -48,6 +47,7 @@ class JobSanity(OptimizerExecutor):
|
|
|
48
47
|
ownerName = manifest.getOption("Owner")
|
|
49
48
|
ownerGroup = manifest.getOption("OwnerGroup")
|
|
50
49
|
isbList = manifest.getOption("InputSandbox", [])
|
|
50
|
+
vo = manifest.getOption("VirtualOrganization")
|
|
51
51
|
sbsToAssign = []
|
|
52
52
|
for isb in isbList:
|
|
53
53
|
if isb.startswith("SB:"):
|
|
@@ -55,8 +55,9 @@ class JobSanity(OptimizerExecutor):
|
|
|
55
55
|
numSBsToAssign = len(sbsToAssign)
|
|
56
56
|
if not numSBsToAssign:
|
|
57
57
|
return S_OK(0)
|
|
58
|
-
self.jobLog.info("Assigning sandboxes", f"({numSBsToAssign} on behalf of {ownerName}@{ownerGroup})")
|
|
59
|
-
|
|
58
|
+
self.jobLog.info("Assigning sandboxes", f"({numSBsToAssign} on behalf of {ownerName}@{ownerGroup}@{vo})")
|
|
59
|
+
eId = f"Job:{jobState.jid}"
|
|
60
|
+
result = SandboxMetadataDB().assignSandboxesToEntities({eId: sbsToAssign}, ownerName, ownerGroup)
|
|
60
61
|
if not result["OK"]:
|
|
61
62
|
self.jobLog.error("Could not assign sandboxes in the SandboxStore")
|
|
62
63
|
return result
|
|
@@ -549,6 +549,10 @@ class JobScheduling(OptimizerExecutor):
|
|
|
549
549
|
if not result["OK"]:
|
|
550
550
|
return result
|
|
551
551
|
|
|
552
|
+
result = jobState.commitChanges()
|
|
553
|
+
if not result["OK"]:
|
|
554
|
+
self.jobLog.error("Could not save changes for job", result["Message"])
|
|
555
|
+
|
|
552
556
|
return S_OK(stageLFNs)
|
|
553
557
|
|
|
554
558
|
def __updateSharedSESites(self, jobManifest, stageSite, stagedLFNs, opData):
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import functools
|
|
4
|
+
import time
|
|
3
5
|
from datetime import datetime, timezone
|
|
4
|
-
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
5
7
|
|
|
6
8
|
from DIRAC.Core.Security.DiracX import DiracXClient, FutureClient, addRPCStub
|
|
7
|
-
from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue
|
|
9
|
+
from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue, returnValueOrRaise
|
|
8
10
|
from DIRAC.Core.Utilities.TimeUtilities import fromString
|
|
11
|
+
from DIRAC.WorkloadManagementSystem.Client import JobStatus
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from diracx.client.models import JobCommand
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
def stripValueIfOK(func):
|
|
@@ -21,30 +27,49 @@ def stripValueIfOK(func):
|
|
|
21
27
|
def wrapper(*args, **kwargs):
|
|
22
28
|
result = func(*args, **kwargs)
|
|
23
29
|
if result.get("OK"):
|
|
24
|
-
|
|
30
|
+
if result.get("Value") is None:
|
|
31
|
+
result.pop("Value")
|
|
25
32
|
return result
|
|
26
33
|
|
|
27
34
|
return wrapper
|
|
28
35
|
|
|
29
36
|
|
|
30
37
|
class JobStateUpdateClient(FutureClient):
|
|
31
|
-
@stripValueIfOK
|
|
32
38
|
@convertToReturnValue
|
|
33
39
|
def sendHeartBeat(self, jobID: str | int, dynamicData: dict, staticData: dict):
|
|
34
|
-
|
|
40
|
+
"""Send a heartbeat from a Job.
|
|
41
|
+
|
|
42
|
+
The behaviour of this function is not strictly the same as in legacy
|
|
43
|
+
DIRAC. Most notably, in legacy DIRAC the heartbeat always overrides the
|
|
44
|
+
job status to Running whereas in DiracX the job state machine is still
|
|
45
|
+
respected. Additionally, DiracX updates the job logging information
|
|
46
|
+
when status transitions occur as a result of the heartbeat.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
with DiracXClient() as api:
|
|
50
|
+
body = {jobID: dynamicData | staticData}
|
|
51
|
+
if len(body[jobID]) != len(dynamicData) + len(staticData):
|
|
52
|
+
raise NotImplementedError(f"Duplicate keys: {dynamicData=} {staticData=}")
|
|
53
|
+
commands: list[JobCommand] = api.jobs.add_heartbeat(body)
|
|
54
|
+
# Legacy DIRAC returns a dictionary of {command: arguments}
|
|
55
|
+
result = {}
|
|
56
|
+
for command in commands:
|
|
57
|
+
if command.job_id != jobID:
|
|
58
|
+
raise NotImplementedError(f"Job ID mismatch: {jobID=} {command.job_id=}")
|
|
59
|
+
result[command.command] = command.arguments
|
|
60
|
+
return result
|
|
35
61
|
|
|
36
62
|
@stripValueIfOK
|
|
37
63
|
@convertToReturnValue
|
|
38
64
|
def setJobApplicationStatus(self, jobID: str | int, appStatus: str, source: str = "Unknown"):
|
|
39
65
|
statusDict = {
|
|
40
|
-
"
|
|
66
|
+
"ApplicationStatus": appStatus,
|
|
41
67
|
}
|
|
42
68
|
if source:
|
|
43
69
|
statusDict["Source"] = source
|
|
44
70
|
with DiracXClient() as api:
|
|
45
|
-
api.jobs.
|
|
46
|
-
jobID,
|
|
47
|
-
{datetime.now(tz=timezone.utc): statusDict},
|
|
71
|
+
api.jobs.set_job_statuses(
|
|
72
|
+
{jobID: {datetime.now(tz=timezone.utc): statusDict}},
|
|
48
73
|
)
|
|
49
74
|
|
|
50
75
|
@stripValueIfOK
|
|
@@ -52,34 +77,29 @@ class JobStateUpdateClient(FutureClient):
|
|
|
52
77
|
def setJobAttribute(self, jobID: str | int, attribute: str, value: str):
|
|
53
78
|
with DiracXClient() as api:
|
|
54
79
|
if attribute == "Status":
|
|
55
|
-
api.jobs.
|
|
56
|
-
jobID,
|
|
57
|
-
{datetime.now(tz=timezone.utc): {"status": value}},
|
|
80
|
+
return api.jobs.set_job_statuses(
|
|
81
|
+
{jobID: {datetime.now(tz=timezone.utc): {"Status": value}}},
|
|
58
82
|
)
|
|
59
83
|
else:
|
|
60
|
-
api.jobs.
|
|
61
|
-
|
|
62
|
-
@stripValueIfOK
|
|
63
|
-
@convertToReturnValue
|
|
64
|
-
def setJobFlag(self, jobID: str | int, flag: str):
|
|
65
|
-
with DiracXClient() as api:
|
|
66
|
-
api.jobs.set_single_job_properties(jobID, {flag: True})
|
|
84
|
+
return api.jobs.patch_metadata({jobID: {attribute: value}})
|
|
67
85
|
|
|
68
86
|
@stripValueIfOK
|
|
69
87
|
@convertToReturnValue
|
|
70
88
|
def setJobParameter(self, jobID: str | int, name: str, value: str):
|
|
71
|
-
|
|
89
|
+
with DiracXClient() as api:
|
|
90
|
+
api.jobs.patch_metadata({jobID: {name: value}})
|
|
72
91
|
|
|
73
92
|
@stripValueIfOK
|
|
74
93
|
@convertToReturnValue
|
|
75
94
|
def setJobParameters(self, jobID: str | int, parameters: list):
|
|
76
|
-
|
|
95
|
+
with DiracXClient() as api:
|
|
96
|
+
api.jobs.patch_metadata({jobID: {k: v for k, v in parameters}})
|
|
77
97
|
|
|
78
98
|
@stripValueIfOK
|
|
79
99
|
@convertToReturnValue
|
|
80
100
|
def setJobSite(self, jobID: str | int, site: str):
|
|
81
101
|
with DiracXClient() as api:
|
|
82
|
-
api.jobs.
|
|
102
|
+
api.jobs.patch_metadata({jobID: {"Site": site}})
|
|
83
103
|
|
|
84
104
|
@stripValueIfOK
|
|
85
105
|
@convertToReturnValue
|
|
@@ -102,9 +122,8 @@ class JobStateUpdateClient(FutureClient):
|
|
|
102
122
|
if datetime_ is None:
|
|
103
123
|
datetime_ = datetime.utcnow()
|
|
104
124
|
with DiracXClient() as api:
|
|
105
|
-
api.jobs.
|
|
106
|
-
jobID,
|
|
107
|
-
{fromString(datetime_).replace(tzinfo=timezone.utc): statusDict},
|
|
125
|
+
api.jobs.set_job_statuses(
|
|
126
|
+
{jobID: {fromString(datetime_).replace(tzinfo=timezone.utc): statusDict}},
|
|
108
127
|
force=force,
|
|
109
128
|
)
|
|
110
129
|
|
|
@@ -114,7 +133,7 @@ class JobStateUpdateClient(FutureClient):
|
|
|
114
133
|
def setJobStatusBulk(self, jobID: str | int, statusDict: dict, force=False):
|
|
115
134
|
statusDict = {fromString(k).replace(tzinfo=timezone.utc): v for k, v in statusDict.items()}
|
|
116
135
|
with DiracXClient() as api:
|
|
117
|
-
api.jobs.
|
|
136
|
+
api.jobs.set_job_statuses(
|
|
118
137
|
{jobID: statusDict},
|
|
119
138
|
force=force,
|
|
120
139
|
)
|
|
@@ -122,13 +141,36 @@ class JobStateUpdateClient(FutureClient):
|
|
|
122
141
|
@stripValueIfOK
|
|
123
142
|
@convertToReturnValue
|
|
124
143
|
def setJobsParameter(self, jobsParameterDict: dict):
|
|
125
|
-
|
|
144
|
+
with DiracXClient() as api:
|
|
145
|
+
updates = {job_id: {k: v} for job_id, (k, v) in jobsParameterDict.items()}
|
|
146
|
+
api.jobs.patch_metadata(updates)
|
|
126
147
|
|
|
127
148
|
@stripValueIfOK
|
|
128
149
|
@convertToReturnValue
|
|
129
|
-
def unsetJobFlag(self, jobID: str | int, flag: str):
|
|
130
|
-
with DiracXClient() as api:
|
|
131
|
-
api.jobs.set_single_job_properties(jobID, {flag: False})
|
|
132
|
-
|
|
133
150
|
def updateJobFromStager(self, jobID: str | int, status: str):
|
|
134
|
-
|
|
151
|
+
if status == "Done":
|
|
152
|
+
jobStatus = JobStatus.CHECKING
|
|
153
|
+
minorStatus = "JobScheduling"
|
|
154
|
+
else:
|
|
155
|
+
jobStatus = None
|
|
156
|
+
minorStatus = "Staging input files failed"
|
|
157
|
+
|
|
158
|
+
trials = 10
|
|
159
|
+
query = [{"parameter": "JobID", "operator": "eq", "value": jobID}]
|
|
160
|
+
with DiracXClient() as api:
|
|
161
|
+
for i in range(trials):
|
|
162
|
+
result = api.jobs.search(parameters=["Status"], search=query)
|
|
163
|
+
if not result:
|
|
164
|
+
return None
|
|
165
|
+
if result[0]["Status"] == JobStatus.STAGING:
|
|
166
|
+
break
|
|
167
|
+
time.sleep(1)
|
|
168
|
+
else:
|
|
169
|
+
return f"Job is not in Staging after {trials} seconds"
|
|
170
|
+
|
|
171
|
+
retVal = self.setJobStatus(jobID, status=jobStatus, minorStatus=minorStatus, source="StagerSystem")
|
|
172
|
+
# As there might not be a value (see stripValueIfOK), only call
|
|
173
|
+
# returnValueOrRaise if the return value is not OK
|
|
174
|
+
if not retVal["OK"]: # pylint: disable=unsubscriptable-object
|
|
175
|
+
returnValueOrRaise(retVal)
|
|
176
|
+
return None if i == 0 else f"Found job in Staging after {i} seconds"
|