blob: 0b26d2e51f85a36afbfc355e398c07b9d0e5fd1b [file] [log] [blame]
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -07001# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07005"""Services relating to DUT status and job history.
6
7The central abstraction of this module is the `HostJobHistory`
8class. This class provides two related pieces of information
9regarding a single DUT:
10 * A history of tests and special tasks that have run on
11 the DUT in a given time range.
12 * Whether the DUT was "working" or "broken" at a given
13 time.
14
15The "working" or "broken" status of a DUT is determined by
16the DUT's special task history. At the end of any job or
17task, the status is indicated as follows:
18 * After any successful special task, the DUT is considered
19 "working".
20 * After any failed Repair task, the DUT is considered "broken".
21 * After any other special task or after any regular test job, the
22 DUT's status is considered unchanged.
23
24Definitions for terms used in the code below:
25 * status task - Any special task that determines the DUT's
26 status; that is, any successful task, or any failed Repair.
27 * diagnosis interval - A time interval during which DUT status
28 changed either from "working" to "broken", or vice versa. The
29 interval starts with the last status task with the old status,
30 and ends after the first status task with the new status.
31
32Diagnosis intervals are interesting because they normally contain
33the logs explaining a failure or repair event.
34
35"""
36
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -070037import common
J. Richard Barnette39255fa2015-04-14 17:23:41 -070038from autotest_lib.frontend import setup_django_environment
39from django.db import models as django_models
40
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -070041from autotest_lib.client.common_lib import global_config
42from autotest_lib.client.common_lib import time_utils
J. Richard Barnette39255fa2015-04-14 17:23:41 -070043from autotest_lib.frontend.afe import models as afe_models
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -070044from autotest_lib.site_utils.suite_scheduler import constants
45
46
47# Values used to describe the diagnosis of a DUT. These values are
48# used to indicate both DUT status after a job or task, and also
49# diagnosis of whether the DUT was working at the end of a given
50# time interval.
51#
52# UNUSED: Used when there are no events recorded in a given
53# time interval.
54# UNKNOWN: For an individual event, indicates that the DUT status
55# is unchanged from the previous event. For a time interval,
56# indicates that the DUT's status can't be determined from the
57# DUT's history.
58# WORKING: Indicates that the DUT was working normally after the
59# event, or at the end of the time interval.
60# BROKEN: Indicates that the DUT needed manual repair after the
61# event, or at the end of the time interval.
62#
63UNUSED = 0
64UNKNOWN = 1
65WORKING = 2
66BROKEN = 3
67
68
69def parse_time(time_string):
70 """Parse time according to a canonical form.
71
72 The "canonical" form is the form in which date/time
73 values are stored in the database.
74
75 @param time_string Time to be parsed.
76 """
77 return int(time_utils.to_epoch_time(time_string))
78
79
80class _JobEvent(object):
81 """Information about an event in host history.
82
83 This remembers the relevant data from a single event in host
84 history. An event is any change in DUT state caused by a job
85 or special task. The data captured are the start and end times
86 of the event, the URL of logs to the job or task causing the
87 event, and a diagnosis of whether the DUT was working or failed
88 afterwards.
89
90 This class is an adapter around the database model objects
91 describing jobs and special tasks. This is an abstract
92 superclass, with concrete subclasses for `HostQueueEntry` and
93 `SpecialTask` objects.
94
95 @property start_time Time the job or task began execution.
96 @property end_time Time the job or task finished execution.
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -080097 @property id id of the event in the AFE database.
98 @property name Name of the event, derived from the AFE database.
99 @property job_status Short string describing the event's final status.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700100 @property job_url URL to the logs for the event's job.
101 @property diagnosis Working status of the DUT after the event.
102
103 """
104
105 get_config_value = global_config.global_config.get_config_value
106 _LOG_URL_PATTERN = get_config_value('CROS', 'log_url_pattern')
107
108 @classmethod
109 def get_log_url(cls, afe_hostname, logdir):
110 """Return a URL to job results.
111
112 The URL is constructed from a base URL determined by the
113 global config, plus the relative path of the job's log
114 directory.
115
116 @param afe_hostname Hostname for autotest frontend
117 @param logdir Relative path of the results log directory.
118
119 @return A URL to the requested results log.
120
121 """
122 return cls._LOG_URL_PATTERN % (afe_hostname, logdir)
123
124
125 def __init__(self, start_time, end_time):
126 self.start_time = parse_time(start_time)
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700127 self.end_time = parse_time(end_time)
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700128
129
130 def __cmp__(self, other):
131 """Compare two jobs by their start time.
132
133 This is a standard Python `__cmp__` method to allow sorting
134 `_JobEvent` objects by their times.
135
136 @param other The `_JobEvent` object to compare to `self`.
137
138 """
139 return self.start_time - other.start_time
140
141
142 @property
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -0800143 def id(self):
144 """Return the id of the event in the AFE database."""
145 raise NotImplemented()
146
147
148 @property
149 def name(self):
150 """Return the name of the event."""
151 raise NotImplemented()
152
153
154 @property
155 def job_status(self):
156 """Return a short string describing the event's final status."""
157 raise NotImplemented()
158
159
160 @property
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700161 def job_url(self):
162 """Return the URL for this event's job logs."""
163 raise NotImplemented()
164
165
166 @property
167 def diagnosis(self):
168 """Return the status of the DUT after this event.
169
170 The diagnosis is interpreted as follows:
171 UNKNOWN - The DUT status was the same before and after
172 the event.
173 WORKING - The DUT appeared to be working after the event.
174 BROKEN - The DUT likely required manual intervention
175 after the event.
176
177 @return A valid diagnosis value.
178
179 """
180 raise NotImplemented()
181
182
183class _SpecialTaskEvent(_JobEvent):
184 """`_JobEvent` adapter for special tasks.
185
186 This class wraps the standard `_JobEvent` interface around a row
187 in the `afe_special_tasks` table.
188
189 """
190
191 @classmethod
192 def get_tasks(cls, afe, host_id, start_time, end_time):
193 """Return special tasks for a host in a given time range.
194
195 Return a list of `_SpecialTaskEvent` objects representing all
196 special tasks that ran on the given host in the given time
197 range. The list is ordered as it was returned by the query
198 (i.e. unordered).
199
200 @param afe Autotest frontend
201 @param host_id Database host id of the desired host.
202 @param start_time Start time of the range of interest.
203 @param end_time End time of the range of interest.
204
205 @return A list of `_SpecialTaskEvent` objects.
206
207 """
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700208 query_start = time_utils.epoch_time_to_date_string(start_time)
209 query_end = time_utils.epoch_time_to_date_string(end_time)
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700210 tasks = afe.get_host_special_tasks(
211 host_id,
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700212 time_started__gte=query_start,
213 time_finished__lte=query_end,
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700214 is_complete=1)
215 return [cls(afe.server, t) for t in tasks]
216
217
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700218 @classmethod
219 def get_status_task(cls, afe, host_id, end_time):
220 """Return the task indicating a host's status at a given time.
221
222 The task returned determines the status of the DUT; the
223 diagnosis on the task indicates the diagnosis for the DUT at
224 the given `end_time`.
225
226 @param afe Autotest frontend
227 @param host_id Database host id of the desired host.
228 @param end_time Find status as of this time.
229
230 @return A `_SpecialTaskEvent` object for the requested task,
231 or `None` if no task was found.
232
233 """
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700234 query_end = time_utils.epoch_time_to_date_string(end_time)
235 task = afe.get_host_status_task(host_id, query_end)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700236 return cls(afe.server, task) if task else None
237
238
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700239 def __init__(self, afe_hostname, afetask):
240 self._afe_hostname = afe_hostname
241 self._afetask = afetask
242 super(_SpecialTaskEvent, self).__init__(
243 afetask.time_started, afetask.time_finished)
244
245
246 @property
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -0800247 def id(self):
248 return self._afetask.id
249
250
251 @property
252 def name(self):
253 return self._afetask.task
254
255
256 @property
257 def job_status(self):
258 if self._afetask.is_aborted:
259 return 'ABORTED'
260 elif self._afetask.success:
261 return 'PASS'
262 else:
263 return 'FAIL'
264
265
266 @property
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700267 def job_url(self):
268 logdir = ('hosts/%s/%s-%s' %
269 (self._afetask.host.hostname, self._afetask.id,
270 self._afetask.task.lower()))
271 return _SpecialTaskEvent.get_log_url(self._afe_hostname, logdir)
272
273
274 @property
275 def diagnosis(self):
276 if self._afetask.success:
277 return WORKING
278 elif self._afetask.task == 'Repair':
279 return BROKEN
280 else:
281 return UNKNOWN
282
283
284class _TestJobEvent(_JobEvent):
285 """`_JobEvent` adapter for regular test jobs.
286
287 This class wraps the standard `_JobEvent` interface around a row
288 in the `afe_host_queue_entries` table.
289
290 """
291
292 @classmethod
293 def get_hqes(cls, afe, host_id, start_time, end_time):
294 """Return HQEs for a host in a given time range.
295
296 Return a list of `_TestJobEvent` objects representing all the
297 HQEs of all the jobs that ran on the given host in the given
298 time range. The list is ordered as it was returned by the
299 query (i.e. unordered).
300
301 @param afe Autotest frontend
302 @param host_id Database host id of the desired host.
303 @param start_time Start time of the range of interest.
304 @param end_time End time of the range of interest.
305
306 @return A list of `_TestJobEvent` objects.
307
308 """
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700309 query_start = time_utils.epoch_time_to_date_string(start_time)
310 query_end = time_utils.epoch_time_to_date_string(end_time)
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700311 hqelist = afe.get_host_queue_entries(
312 host_id=host_id,
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700313 start_time=query_start,
314 end_time=query_end,
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700315 complete=1)
316 return [cls(afe.server, hqe) for hqe in hqelist]
317
318
319 def __init__(self, afe_hostname, hqe):
320 self._afe_hostname = afe_hostname
321 self._hqe = hqe
322 super(_TestJobEvent, self).__init__(
323 hqe.started_on, hqe.finished_on)
324
325
326 @property
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -0800327 def id(self):
328 return self._hqe.id
329
330
331 @property
332 def name(self):
333 return self._hqe.job.name
334
335
336 @property
337 def job_status(self):
338 return self._hqe.status
339
340
341 @property
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700342 def job_url(self):
343 logdir = '%s-%s' % (self._hqe.job.id, self._hqe.job.owner)
344 return _TestJobEvent.get_log_url(self._afe_hostname, logdir)
345
346
347 @property
348 def diagnosis(self):
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700349 return UNKNOWN
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700350
351
352class HostJobHistory(object):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700353 """Class to query and remember DUT execution and status history.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700354
355 This class is responsible for querying the database to determine
356 the history of a single DUT in a time interval of interest, and
357 for remembering the query results for reporting.
358
359 @property hostname Host name of the DUT.
David Rileyd23d24a2016-10-04 20:07:00 -0700360 @property start_time Start of the requested time interval, as a unix
361 timestamp (epoch time).
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700362 This field may be `None`.
David Rileyd23d24a2016-10-04 20:07:00 -0700363 @property end_time End of the requested time interval, as a unix
364 timestamp (epoch time).
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700365 @property _afe Autotest frontend for queries.
366 @property _host Database host object for the DUT.
367 @property _history A list of jobs and special tasks that
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700368 ran on the DUT in the requested time
369 interval, ordered in reverse, from latest
370 to earliest.
371
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700372 @property _status_interval A list of all the jobs and special
373 tasks that ran on the DUT in the
374 last diagnosis interval prior to
375 `end_time`, ordered from latest to
376 earliest.
377 @property _status_diagnosis The DUT's status as of `end_time`.
378 @property _status_task The DUT's last status task as of
379 `end_time`.
380
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700381 """
382
383 @classmethod
384 def get_host_history(cls, afe, hostname, start_time, end_time):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700385 """Create a `HostJobHistory` instance for a single host.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700386
387 Simple factory method to construct host history from a
388 hostname. Simply looks up the host in the AFE database, and
389 passes it to the class constructor.
390
391 @param afe Autotest frontend
392 @param hostname Name of the host.
393 @param start_time Start time for the history's time
394 interval.
395 @param end_time End time for the history's time interval.
396
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700397 @return A new `HostJobHistory` instance.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700398
399 """
400 afehost = afe.get_hosts(hostname=hostname)[0]
401 return cls(afe, afehost, start_time, end_time)
402
403
404 @classmethod
405 def get_multiple_histories(cls, afe, start_time, end_time,
406 board=None, pool=None):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700407 """Create `HostJobHistory` instances for a set of hosts.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700408
409 The set of hosts can be specified as "all hosts of a given
410 board type", "all hosts in a given pool", or "all hosts
411 of a given board and pool".
412
413 @param afe Autotest frontend
414 @param start_time Start time for the history's time
415 interval.
416 @param end_time End time for the history's time interval.
417 @param board All hosts must have this board type; if
418 `None`, all boards are allowed.
419 @param pool All hosts must be in this pool; if
420 `None`, all pools are allowed.
421
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700422 @return A list of new `HostJobHistory` instances.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700423
424 """
425 # If `board` or `pool` are both `None`, we could search the
426 # entire database, which is more expensive than we want.
427 # Our caller currently won't (can't) do this, but assert to
428 # be safe.
429 assert board is not None or pool is not None
430 labels = []
431 if board is not None:
432 labels.append(constants.Labels.BOARD_PREFIX + board)
433 if pool is not None:
434 labels.append(constants.Labels.POOL_PREFIX + pool)
435 kwargs = {'multiple_labels': labels}
436 hosts = afe.get_hosts(**kwargs)
437 return [cls(afe, h, start_time, end_time) for h in hosts]
438
439
440 def __init__(self, afe, afehost, start_time, end_time):
441 self._afe = afe
442 self.hostname = afehost.hostname
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700443 self.end_time = end_time
444 self.start_time = start_time
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700445 self._host = afehost
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700446 # Don't spend time on queries until they're needed.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700447 self._history = None
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700448 self._status_interval = None
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700449 self._status_diagnosis = None
450 self._status_task = None
451
452
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700453 def _get_history(self, start_time, end_time):
454 """Get the list of events for the given interval."""
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700455 newtasks = _SpecialTaskEvent.get_tasks(
456 self._afe, self._host.id, start_time, end_time)
457 newhqes = _TestJobEvent.get_hqes(
458 self._afe, self._host.id, start_time, end_time)
459 newhistory = newtasks + newhqes
460 newhistory.sort(reverse=True)
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700461 return newhistory
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700462
463
464 def __iter__(self):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700465 if self._history is None:
466 self._history = self._get_history(self.start_time,
467 self.end_time)
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700468 return self._history.__iter__()
469
470
J. Richard Barnette96db3492015-03-27 17:23:52 -0700471 def _extract_prefixed_label(self, prefix):
J. Richard Barnette6948ed32015-05-06 08:57:10 -0700472 labels = [l for l in self._host.labels
473 if l.startswith(prefix)]
474 return labels[0][len(prefix) : ] if labels else None
J. Richard Barnette96db3492015-03-27 17:23:52 -0700475
476
J. Richard Barnette3d0590a2015-04-29 12:56:12 -0700477 @property
478 def host(self):
479 """Return the AFE host object for this history."""
480 return self._host
481
482
483 @property
484 def host_board(self):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700485 """Return the board name for this history's DUT."""
486 prefix = constants.Labels.BOARD_PREFIX
487 return self._extract_prefixed_label(prefix)
488
489
J. Richard Barnette3d0590a2015-04-29 12:56:12 -0700490 @property
491 def host_pool(self):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700492 """Return the pool name for this history's DUT."""
493 prefix = constants.Labels.POOL_PREFIX
494 return self._extract_prefixed_label(prefix)
495
496
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700497 def _init_status_task(self):
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700498 """Fill in `self._status_diagnosis` and `_status_task`."""
499 if self._status_diagnosis is not None:
500 return
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700501 self._status_task = _SpecialTaskEvent.get_status_task(
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700502 self._afe, self._host.id, self.end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700503 if self._status_task is not None:
504 self._status_diagnosis = self._status_task.diagnosis
505 else:
506 self._status_diagnosis = UNKNOWN
507
508
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700509 def _init_status_interval(self):
510 """Fill in `self._status_interval`."""
511 if self._status_interval is not None:
512 return
513 self._init_status_task()
514 self._status_interval = []
515 if self._status_task is None:
516 return
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700517 query_end = time_utils.epoch_time_to_date_string(self.end_time)
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700518 interval = self._afe.get_host_diagnosis_interval(
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700519 self._host.id, query_end,
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700520 self._status_diagnosis != WORKING)
521 if not interval:
522 return
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700523 self._status_interval = self._get_history(
524 parse_time(interval[0]),
525 parse_time(interval[1]))
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700526
527
528 def diagnosis_interval(self):
529 """Find this history's most recent diagnosis interval.
530
531 Returns a list of `_JobEvent` instances corresponding to the
532 most recent diagnosis interval occurring before this
533 history's end time.
534
535 The list is returned as with `self._history`, ordered from
536 most to least recent.
537
538 @return The list of the `_JobEvent`s in the diagnosis
539 interval.
540
541 """
542 self._init_status_interval()
543 return self._status_interval
544
545
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700546 def last_diagnosis(self):
547 """Return the diagnosis of whether the DUT is working.
548
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700549 This searches the DUT's job history, looking for the most
550 recent status task for the DUT. Return a tuple of
551 `(diagnosis, task)`.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700552
553 The `diagnosis` entry in the tuple is one of these values:
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700554 * UNUSED - The host's last status task is older than
555 `self.start_time`.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700556 * WORKING - The DUT is working.
557 * BROKEN - The DUT likely requires manual intervention.
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700558 * UNKNOWN - No task could be found indicating status for
559 the DUT.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700560
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700561 If the DUT was working at last check, but hasn't been used
562 inside this history's time interval, the status `UNUSED` is
563 returned with the last status task, instead of `WORKING`.
564
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700565 The `task` entry in the tuple is the status task that led to
566 the diagnosis. The task will be `None` if the diagnosis is
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700567 `UNKNOWN`.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700568
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700569 @return A tuple with the DUT's diagnosis and the task that
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700570 determined it.
571
572 """
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700573 self._init_status_task()
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700574 diagnosis = self._status_diagnosis
575 if (self.start_time is not None and
576 self._status_task is not None and
577 self._status_task.end_time < self.start_time and
578 diagnosis == WORKING):
579 diagnosis = UNUSED
580 return diagnosis, self._status_task
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700581
582
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700583def get_diagnosis_interval(host_id, end_time, success):
584 """Return the last diagnosis interval for a given host and time.
585
586 This routine queries the database for the special tasks on a
587 given host before a given time. From those tasks it selects the
588 last status task before a change in status, and the first status
589 task after the change. When `success` is true, the change must
590 be from "working" to "broken". When false, the search is for a
591 change in the opposite direction.
592
593 A "successful status task" is any successful special task. A
594 "failed status task" is a failed Repair task. These criteria
595 are based on the definition of "status task" in the module-level
596 docstring, above.
597
598 This is the RPC endpoint for `AFE.get_host_diagnosis_interval()`.
599
600 @param host_id Database host id of the desired host.
601 @param end_time Find the last eligible interval before this time.
602 @param success Whether the eligible interval should start with a
603 success or a failure.
604
605 @return A list containing the start time of the earliest job
606 selected, and the end time of the latest job.
607
608 """
609 base_query = afe_models.SpecialTask.objects.filter(
610 host_id=host_id, is_complete=True)
611 success_query = base_query.filter(success=True)
612 failure_query = base_query.filter(success=False, task='Repair')
613 if success:
614 query0 = success_query
615 query1 = failure_query
616 else:
617 query0 = failure_query
618 query1 = success_query
619 query0 = query0.filter(time_finished__lte=end_time)
620 query0 = query0.order_by('time_started').reverse()
621 if not query0:
622 return []
623 task0 = query0[0]
624 query1 = query1.filter(time_finished__gt=task0.time_finished)
625 task1 = query1.order_by('time_started')[0]
626 return [task0.time_started.strftime(time_utils.TIME_FMT),
627 task1.time_finished.strftime(time_utils.TIME_FMT)]
628
629
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700630def get_status_task(host_id, end_time):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700631 """Get the last status task for a host before a given time.
632
633 This routine returns a Django query for the AFE database to find
634 the last task that finished on the given host before the given
635 time that was either a successful task, or a Repair task. The
636 query criteria are based on the definition of "status task" in
637 the module-level docstring, above.
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700638
639 This is the RPC endpoint for `_SpecialTaskEvent.get_status_task()`.
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700640
641 @param host_id Database host id of the desired host.
642 @param end_time End time of the range of interest.
643
644 @return A Django query-set selecting the single special task of
645 interest.
646
647 """
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700648 # Selects status tasks: any Repair task, or any successful task.
649 status_tasks = (django_models.Q(task='Repair') |
650 django_models.Q(success=True))
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700651 # Our caller needs a Django query set in order to serialize the
652 # result, so we don't resolve the query here; we just return a
653 # slice with at most one element.
654 return afe_models.SpecialTask.objects.filter(
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700655 status_tasks,
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700656 host_id=host_id,
657 time_finished__lte=end_time,
658 is_complete=True).order_by('time_started').reverse()[0:1]