1
1
"""Utility to set the job status in the jobDB"""
2
2
3
- import datetime
3
+ from __future__ import annotations
4
4
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
6
9
from DIRAC .ConfigurationSystem .Client .Helpers .Operations import Operations
7
10
from DIRAC .Core .Utilities import TimeUtilities
8
11
from DIRAC .Core .Utilities .ObjectLoader import ObjectLoader
9
12
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
13
18
14
19
15
20
class JobStatusUtility :
@@ -81,7 +86,7 @@ def setJobStatus(
81
86
if source :
82
87
sDict ["Source" ] = source
83
88
if not dateTime :
84
- dateTime = str (datetime .datetime . utcnow ())
89
+ dateTime = str (datetime .utcnow ())
85
90
return self .setJobStatusBulk (jobID , {dateTime : sDict }, force = force )
86
91
return S_OK ()
87
92
@@ -133,60 +138,16 @@ def setJobStatusBulk(self, jobID: int, statusDict: dict, force: bool = False):
133
138
updateTimes = sorted (statusDict )
134
139
log .debug ("*** New call ***" , f"Last update time { lastTime } - Sorted new times { updateTimes } " )
135
140
# 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 )
154
142
155
143
# We should only update the status to the last one if its time stamp is more recent than the last update
156
144
attrNames = []
157
145
attrValues = []
158
146
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" ]
190
151
log .debug ("Final statuses:" , f"status '{ status } ', minor '{ minor } ', application '{ application } '" )
191
152
if status :
192
153
attrNames .append ("Status" )
@@ -206,11 +167,13 @@ def setJobStatusBulk(self, jobID: int, statusDict: dict, force: bool = False):
206
167
if not result ["OK" ]:
207
168
return result
208
169
# Update start and end time if needed
209
- if endTime :
170
+ if not endTime and newEndTime :
171
+ log .debug ("Set job end time" , endTime )
210
172
result = self .jobDB .setEndExecTime (jobID , endTime )
211
173
if not result ["OK" ]:
212
174
return result
213
- if startTime :
175
+ if not startTime and newStartTime :
176
+ log .debug ("Set job start time" , startTime )
214
177
result = self .jobDB .setStartExecTime (jobID , startTime )
215
178
if not result ["OK" ]:
216
179
return result
@@ -237,3 +200,66 @@ def setJobStatusBulk(self, jobID: int, statusDict: dict, force: bool = False):
237
200
return result
238
201
239
202
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"\t Time { 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