Skip to content

Commit e20e0f1

Browse files
authored
Merge pull request #7189 from simon-mazenoux/feat-factorize-jobstatusutiliy
[8.1] Refactor JobStatusUtility to reuse the logic in diracx
2 parents 262eab1 + 76986e8 commit e20e0f1

File tree

1 file changed

+83
-57
lines changed

1 file changed

+83
-57
lines changed

src/DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py

Lines changed: 83 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
"""Utility to set the job status in the jobDB"""
22

3-
import datetime
3+
from __future__ import annotations
44

5-
from DIRAC import gLogger, S_OK, S_ERROR
5+
from datetime import datetime
6+
from typing import TYPE_CHECKING, Any
7+
8+
from DIRAC import S_ERROR, S_OK, gLogger
69
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
710
from DIRAC.Core.Utilities import TimeUtilities
811
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader
912
from DIRAC.WorkloadManagementSystem.Client import JobStatus
10-
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
11-
from DIRAC.WorkloadManagementSystem.DB.JobLoggingDB import JobLoggingDB
12-
from DIRAC.WorkloadManagementSystem.DB.ElasticJobParametersDB import ElasticJobParametersDB
13+
14+
if TYPE_CHECKING:
15+
from DIRAC.WorkloadManagementSystem.DB.ElasticJobParametersDB import ElasticJobParametersDB
16+
from DIRAC.WorkloadManagementSystem.DB.JobDB import JobDB
17+
from DIRAC.WorkloadManagementSystem.DB.JobLoggingDB import JobLoggingDB
1318

1419

1520
class JobStatusUtility:
@@ -81,7 +86,7 @@ def setJobStatus(
8186
if source:
8287
sDict["Source"] = source
8388
if not dateTime:
84-
dateTime = str(datetime.datetime.utcnow())
89+
dateTime = str(datetime.utcnow())
8590
return self.setJobStatusBulk(jobID, {dateTime: sDict}, force=force)
8691
return S_OK()
8792

@@ -133,60 +138,16 @@ def setJobStatusBulk(self, jobID: int, statusDict: dict, force: bool = False):
133138
updateTimes = sorted(statusDict)
134139
log.debug("*** New call ***", f"Last update time {lastTime} - Sorted new times {updateTimes}")
135140
# Get the status (if any) at the time of the first update
136-
newStat = ""
137-
firstUpdate = TimeUtilities.toEpoch(TimeUtilities.fromString(updateTimes[0]))
138-
for ts, st in timeStamps:
139-
if firstUpdate >= ts:
140-
newStat = st
141-
# Pick up start and end times from all updates
142-
for updTime in updateTimes:
143-
sDict = statusDict[updTime]
144-
newStat = sDict.get("Status", newStat)
145-
146-
if not startTime and newStat == JobStatus.RUNNING:
147-
# Pick up the start date when the job starts running if not existing
148-
startTime = updTime
149-
log.debug("Set job start time", startTime)
150-
elif not endTime and newStat in JobStatus.JOB_FINAL_STATES:
151-
# Pick up the end time when the job is in a final status
152-
endTime = updTime
153-
log.debug("Set job end time", endTime)
141+
newStartTime, newEndTime = getStartAndEndTime(startTime, endTime, updateTimes, timeStamps, statusDict)
154142

155143
# We should only update the status to the last one if its time stamp is more recent than the last update
156144
attrNames = []
157145
attrValues = []
158146
if updateTimes[-1] >= lastTime:
159-
minor = ""
160-
application = ""
161-
# Get the last status values looping on the most recent upupdateTimes in chronological order
162-
for updTime in [dt for dt in updateTimes if dt >= lastTime]:
163-
sDict = statusDict[updTime]
164-
log.debug("\t", f"Time {updTime} - Statuses {str(sDict)}")
165-
status = sDict.get("Status", currentStatus)
166-
# evaluate the state machine if the status is changing
167-
if not force and status != currentStatus:
168-
res = JobStatus.JobsStateMachine(currentStatus).getNextState(status)
169-
if not res["OK"]:
170-
return res
171-
newStat = res["Value"]
172-
# If the JobsStateMachine does not accept the candidate, don't update
173-
if newStat != status:
174-
# keeping the same status
175-
log.error(
176-
"Job Status Error",
177-
f"{jobID} can't move from {currentStatus} to {status}: using {newStat}",
178-
)
179-
status = newStat
180-
sDict["Status"] = newStat
181-
# Change the source to indicate this is not what was requested
182-
source = sDict.get("Source", "")
183-
sDict["Source"] = source + "(SM)"
184-
# at this stage status == newStat. Set currentStatus to this new status
185-
currentStatus = newStat
186-
187-
minor = sDict.get("MinorStatus", minor)
188-
application = sDict.get("ApplicationStatus", application)
189-
147+
res = getNewStatus(jobID, updateTimes, lastTime, statusDict, currentStatus, force, log)
148+
if not res["OK"]:
149+
return res
150+
status, minor, application = res["Value"]
190151
log.debug("Final statuses:", f"status '{status}', minor '{minor}', application '{application}'")
191152
if status:
192153
attrNames.append("Status")
@@ -206,11 +167,13 @@ def setJobStatusBulk(self, jobID: int, statusDict: dict, force: bool = False):
206167
if not result["OK"]:
207168
return result
208169
# Update start and end time if needed
209-
if endTime:
170+
if not endTime and newEndTime:
171+
log.debug("Set job end time", endTime)
210172
result = self.jobDB.setEndExecTime(jobID, endTime)
211173
if not result["OK"]:
212174
return result
213-
if startTime:
175+
if not startTime and newStartTime:
176+
log.debug("Set job start time", startTime)
214177
result = self.jobDB.setStartExecTime(jobID, startTime)
215178
if not result["OK"]:
216179
return result
@@ -237,3 +200,66 @@ def setJobStatusBulk(self, jobID: int, statusDict: dict, force: bool = False):
237200
return result
238201

239202
return S_OK((attrNames, attrValues))
203+
204+
205+
def getStartAndEndTime(startTime, endTime, updateTimes, timeStamps, statusDict):
206+
newStat = ""
207+
firstUpdate = TimeUtilities.toEpoch(TimeUtilities.fromString(updateTimes[0]))
208+
for ts, st in timeStamps:
209+
if firstUpdate >= ts:
210+
newStat = st
211+
# Pick up start and end times from all updates
212+
for updTime in updateTimes:
213+
sDict = statusDict[updTime]
214+
newStat = sDict.get("Status", newStat)
215+
216+
if not startTime and newStat == JobStatus.RUNNING:
217+
# Pick up the start date when the job starts running if not existing
218+
startTime = updTime
219+
elif not endTime and newStat in JobStatus.JOB_FINAL_STATES:
220+
# Pick up the end time when the job is in a final status
221+
endTime = updTime
222+
223+
return startTime, endTime
224+
225+
226+
def getNewStatus(
227+
jobID: int,
228+
updateTimes: list[datetime],
229+
lastTime: datetime,
230+
statusDict: dict[datetime, Any],
231+
currentStatus,
232+
force: bool,
233+
log,
234+
):
235+
status = ""
236+
minor = ""
237+
application = ""
238+
# Get the last status values looping on the most recent upupdateTimes in chronological order
239+
for updTime in [dt for dt in updateTimes if dt >= lastTime]:
240+
sDict = statusDict[updTime]
241+
log.debug(f"\tTime {updTime} - Statuses {str(sDict)}")
242+
status = sDict.get("Status", currentStatus)
243+
# evaluate the state machine if the status is changing
244+
if not force and status != currentStatus:
245+
res = JobStatus.JobsStateMachine(currentStatus).getNextState(status)
246+
if not res["OK"]:
247+
return res
248+
newStat = res["Value"]
249+
# If the JobsStateMachine does not accept the candidate, don't update
250+
if newStat != status:
251+
# keeping the same status
252+
log.error(
253+
f"Job Status Error: {jobID} can't move from {currentStatus} to {status}: using {newStat}",
254+
)
255+
status = newStat
256+
sDict["Status"] = newStat
257+
# Change the source to indicate this is not what was requested
258+
source = sDict.get("Source", "")
259+
sDict["Source"] = source + "(SM)"
260+
# at this stage status == newStat. Set currentStatus to this new status
261+
currentStatus = newStat
262+
263+
minor = sDict.get("MinorStatus", minor)
264+
application = sDict.get("ApplicationStatus", application)
265+
return S_OK((status, minor, application))

0 commit comments

Comments
 (0)