Skip to content

Commit 1a28820

Browse files
committed
refactor: extracted utilities from WMSClient
1 parent 7431a4b commit 1a28820

6 files changed

Lines changed: 187 additions & 176 deletions

File tree

src/DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobCleaningAgent.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def jca(mocker):
3232
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.JobDB.selectJobs", side_effect=mockReply)
3333
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.JobDB.__init__", side_effect=mockNone)
3434
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.ReqClient", return_value=mockNone)
35-
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.JobMonitoringClient", return_value=mockJMC)
3635

3736
jca = JobCleaningAgent()
3837
jca.log = gLogger
@@ -127,7 +126,6 @@ def test_deleteJobOversizedSandbox(mocker, inputs, params, expected):
127126
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.AgentModule.am_getOption", return_value=mockAM)
128127
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.JobDB", return_value=mockNone)
129128
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.ReqClient", return_value=mockNone)
130-
mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.JobMonitoringClient", return_value=mockJMC)
131129
mocker.patch(
132130
"DIRAC.WorkloadManagementSystem.Agent.JobCleaningAgent.getDNForUsername", return_value=S_OK(["/bih/boh/DN"])
133131
)

src/DIRAC/WorkloadManagementSystem/Client/WMSClient.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
methods necessary to communicate with the Workload Management System
33
"""
44
import os
5-
from io import StringIO
65
import time
6+
from io import StringIO
77

8-
from DIRAC import S_OK, S_ERROR, gLogger
9-
8+
from DIRAC import S_ERROR, S_OK, gLogger
109
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
1110
from DIRAC.Core.Utilities import File
1211
from DIRAC.Core.Utilities.ClassAd.ClassAdLight import ClassAd

src/DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py

Lines changed: 29 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
from DIRAC.Core.Utilities.JEncode import strToIntDict
2222
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
2323
from DIRAC.FrameworkSystem.Client.ProxyManagerClient import gProxyManager
24-
from DIRAC.StorageManagementSystem.DB.StorageManagementDB import StorageManagementDB
2524
from DIRAC.WorkloadManagementSystem.Client import JobStatus
26-
from DIRAC.WorkloadManagementSystem.Client.JobStatus import filterJobStateTransition
2725
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import (
2826
RIGHT_DELETE,
2927
RIGHT_KILL,
@@ -32,6 +30,7 @@
3230
RIGHT_SUBMIT,
3331
JobPolicy,
3432
)
33+
from DIRAC.WorkloadManagementSystem.Utilities.jobAdministration import kill_delete_jobs
3534
from DIRAC.WorkloadManagementSystem.Utilities.JobModel import JobDescriptionModel
3635
from DIRAC.WorkloadManagementSystem.Utilities.ParametricJob import generateParametricJobs, getParameterVectorLength
3736
from DIRAC.WorkloadManagementSystem.Utilities.Utils import rescheduleJobs
@@ -389,6 +388,8 @@ def export_removeJob(self, jobIDs):
389388
:return: S_OK()/S_ERROR() -- confirmed job IDs
390389
"""
391390

391+
# FIXME: extract the logic to a utility function
392+
392393
jobList = self.__getJobList(jobIDs)
393394
if not jobList:
394395
return S_ERROR("Invalid job specification: " + str(jobIDs))
@@ -440,131 +441,28 @@ def export_removeJob(self, jobIDs):
440441

441442
return S_OK(validJobList)
442443

443-
def __deleteJob(self, jobID, force=False):
444-
"""Set the job status to "Deleted"
445-
and remove the pilot that ran and its logging info if the pilot is finished.
446-
447-
:param int jobID: job ID
448-
:return: S_OK()/S_ERROR()
449-
"""
450-
if not (result := self.jobDB.setJobStatus(jobID, JobStatus.DELETED, "Checking accounting", force=force))["OK"]:
451-
return result
452-
453-
if not (result := self.taskQueueDB.deleteJob(jobID))["OK"]:
454-
self.log.warn("Failed to delete job from the TaskQueue")
455-
456-
# if it was the last job for the pilot
457-
result = self.pilotAgentsDB.getPilotsForJobID(jobID)
458-
if not result["OK"]:
459-
self.log.error("Failed to get Pilots for JobID", result["Message"])
460-
return result
461-
for pilot in result["Value"]:
462-
res = self.pilotAgentsDB.getJobsForPilot(pilot)
463-
if not res["OK"]:
464-
self.log.error("Failed to get jobs for pilot", res["Message"])
465-
return res
466-
if not res["Value"]: # if list of jobs for pilot is empty, delete pilot
467-
result = self.pilotAgentsDB.getPilotInfo(pilotID=pilot)
468-
if not result["OK"]:
469-
self.log.error("Failed to get pilot info", result["Message"])
470-
return result
471-
ret = self.pilotAgentsDB.deletePilot(result["Value"]["PilotJobReference"])
472-
if not ret["OK"]:
473-
self.log.error("Failed to delete pilot from PilotAgentsDB", ret["Message"])
474-
return ret
475-
476-
return S_OK()
444+
###########################################################################
445+
types_deleteJob = []
477446

478-
def __killJob(self, jobID, sendKillCommand=True, force=False):
479-
"""Kill one job
447+
def export_deleteJob(self, jobIDs, force=False):
448+
"""Delete jobs specified in the jobIDs list
480449
481-
:param int jobID: job ID
482-
:param bool sendKillCommand: send kill command
450+
:param list jobIDs: list of job IDs
483451
484-
:return: S_OK()/S_ERROR()
452+
:return: S_OK/S_ERROR
485453
"""
486-
if sendKillCommand:
487-
if not (result := self.jobDB.setJobCommand(jobID, "Kill"))["OK"]:
488-
return result
489454

490-
self.log.info("Job marked for termination", jobID)
491-
if not (result := self.jobDB.setJobStatus(jobID, JobStatus.KILLED, "Marked for termination", force=force))[
492-
"OK"
493-
]:
494-
self.log.warn("Failed to set job Killed status", result["Message"])
495-
if not (result := self.taskQueueDB.deleteJob(jobID))["OK"]:
496-
self.log.warn("Failed to delete job from the TaskQueue", result["Message"])
497-
498-
return S_OK()
499-
500-
def _kill_delete_jobs(self, jobIDList, right, force=False):
501-
"""Kill (== set the status to "KILLED") or delete (== set the status to "DELETED") jobs as necessary
502-
503-
:param list jobIDList: job IDs
504-
:param str right: RIGHT_KILL or RIGHT_DELETE
505-
506-
:return: S_OK()/S_ERROR()
507-
"""
508-
jobList = self.__getJobList(jobIDList)
455+
jobList = self.__getJobList(jobIDs)
509456
if not jobList:
510457
self.log.warn("No jobs specified")
511458
return S_OK([])
512459

513-
validJobList, invalidJobList, nonauthJobList, ownerJobList = self.jobPolicy.evaluateJobRights(jobList, right)
514-
515-
badIDs = []
516-
517-
killJobList = []
518-
deleteJobList = []
519-
if validJobList:
520-
# Get the jobs allowed to transition to the Killed state
521-
filterRes = filterJobStateTransition(validJobList, JobStatus.KILLED)
522-
if not filterRes["OK"]:
523-
return filterRes
524-
killJobList.extend(filterRes["Value"])
525-
526-
if not right == RIGHT_KILL:
527-
# Get the jobs allowed to transition to the Deleted state
528-
filterRes = filterJobStateTransition(validJobList, JobStatus.DELETED)
529-
if not filterRes["OK"]:
530-
return filterRes
531-
deleteJobList.extend(filterRes["Value"])
532-
533-
# Look for jobs that are in the Staging state to send kill signal to the stager
534-
result = self.jobDB.getJobsAttributes(killJobList, ["Status"])
535-
if not result["OK"]:
536-
return result
537-
stagingJobList = [jobID for jobID, sDict in result["Value"].items() if sDict["Status"] == JobStatus.STAGING]
538-
539-
for jobID in killJobList:
540-
result = self.__killJob(jobID, force=force)
541-
if not result["OK"]:
542-
badIDs.append(jobID)
543-
544-
for jobID in deleteJobList:
545-
result = self.__deleteJob(jobID, force=force)
546-
if not result["OK"]:
547-
badIDs.append(jobID)
460+
validJobList, invalidJobList, nonauthJobList, ownerJobList = self.jobPolicy.evaluateJobRights(
461+
jobList, RIGHT_DELETE
462+
)
548463

549-
if stagingJobList:
550-
stagerDB = StorageManagementDB()
551-
self.log.info("Going to send killing signal to stager as well!")
552-
result = stagerDB.killTasksBySourceTaskID(stagingJobList)
553-
if not result["OK"]:
554-
self.log.warn("Failed to kill some Stager tasks", result["Message"])
464+
result = kill_delete_jobs(RIGHT_DELETE, validJobList, nonauthJobList, force=force)
555465

556-
if nonauthJobList or badIDs:
557-
result = S_ERROR("Some jobs failed deletion")
558-
if nonauthJobList:
559-
self.log.warn("Non-authorized JobIDs won't be deleted", str(nonauthJobList))
560-
result["NonauthorizedJobIDs"] = nonauthJobList
561-
if badIDs:
562-
self.log.warn("JobIDs failed to be deleted", str(badIDs))
563-
result["FailedJobIDs"] = badIDs
564-
return result
565-
566-
jobsList = killJobList if right == RIGHT_KILL else deleteJobList
567-
result = S_OK(jobsList)
568466
result["requireProxyUpload"] = len(ownerJobList) > 0 and self.__checkIfProxyUploadIsRequired()
569467

570468
if invalidJobList:
@@ -573,30 +471,33 @@ def _kill_delete_jobs(self, jobIDList, right, force=False):
573471
return result
574472

575473
###########################################################################
576-
types_deleteJob = []
474+
types_killJob = []
577475

578-
def export_deleteJob(self, jobIDs, force=False):
579-
"""Delete jobs specified in the jobIDs list
476+
def export_killJob(self, jobIDs, force=False):
477+
"""Kill jobs specified in the jobIDs list
580478
581479
:param list jobIDs: list of job IDs
582480
583481
:return: S_OK/S_ERROR
584482
"""
585483

586-
return self._kill_delete_jobs(jobIDs, RIGHT_DELETE, force=force)
484+
jobList = self.__getJobList(jobIDs)
485+
if not jobList:
486+
self.log.warn("No jobs specified")
487+
return S_OK([])
587488

588-
###########################################################################
589-
types_killJob = []
489+
validJobList, invalidJobList, nonauthJobList, ownerJobList = self.jobPolicy.evaluateJobRights(
490+
jobList, RIGHT_KILL
491+
)
590492

591-
def export_killJob(self, jobIDs, force=False):
592-
"""Kill jobs specified in the jobIDs list
493+
result = kill_delete_jobs(RIGHT_KILL, validJobList, nonauthJobList, force=force)
593494

594-
:param list jobIDs: list of job IDs
495+
result["requireProxyUpload"] = len(ownerJobList) > 0 and self.__checkIfProxyUploadIsRequired()
595496

596-
:return: S_OK/S_ERROR
597-
"""
497+
if invalidJobList:
498+
result["InvalidJobIDs"] = invalidJobList
598499

599-
return self._kill_delete_jobs(jobIDs, RIGHT_KILL, force=force)
500+
return result
600501

601502
###########################################################################
602503
types_resetJob = []
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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.Client.JobStatus import filterJobStateTransition
5+
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
6+
from DIRAC.WorkloadManagementSystem.DB.PilotAgentsDB import PilotAgentsDB
7+
from DIRAC.WorkloadManagementSystem.DB.TaskQueueDB import TaskQueueDB
8+
from DIRAC.WorkloadManagementSystem.Service.JobPolicy import RIGHT_KILL
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+
# Get the jobs allowed to transition to the Killed state
83+
filterRes = filterJobStateTransition(validJobList, JobStatus.KILLED)
84+
if not filterRes["OK"]:
85+
return filterRes
86+
killJobList.extend(filterRes["Value"])
87+
88+
if not right == RIGHT_KILL:
89+
# Get the jobs allowed to transition to the Deleted state
90+
filterRes = filterJobStateTransition(validJobList, JobStatus.DELETED)
91+
if not filterRes["OK"]:
92+
return filterRes
93+
deleteJobList.extend(filterRes["Value"])
94+
95+
# Look for jobs that are in the Staging state to send kill signal to the stager
96+
result = JobDB().getJobsAttributes(killJobList, ["Status"])
97+
if not result["OK"]:
98+
return result
99+
stagingJobList = [jobID for jobID, sDict in result["Value"].items() if sDict["Status"] == JobStatus.STAGING]
100+
101+
for jobID in killJobList:
102+
result = _killJob(jobID, force=force)
103+
if not result["OK"]:
104+
badIDs.append(jobID)
105+
106+
for jobID in deleteJobList:
107+
result = _deleteJob(jobID, force=force)
108+
if not result["OK"]:
109+
badIDs.append(jobID)
110+
111+
if stagingJobList:
112+
stagerDB = StorageManagementDB()
113+
gLogger.info("Going to send killing signal to stager as well!")
114+
result = stagerDB.killTasksBySourceTaskID(stagingJobList)
115+
if not result["OK"]:
116+
gLogger.warn("Failed to kill some Stager tasks", result["Message"])
117+
118+
if nonauthJobList or badIDs:
119+
result = S_ERROR("Some jobs failed deletion")
120+
if nonauthJobList:
121+
gLogger.warn("Non-authorized JobIDs won't be deleted", str(nonauthJobList))
122+
result["NonauthorizedJobIDs"] = nonauthJobList
123+
if badIDs:
124+
gLogger.warn("JobIDs failed to be deleted", str(badIDs))
125+
result["FailedJobIDs"] = badIDs
126+
return result
127+
128+
jobsList = killJobList if right == RIGHT_KILL else deleteJobList
129+
return S_OK(jobsList)

0 commit comments

Comments
 (0)