blob: 3ce921d3d2f1eb42db6d0df8ff8bc269f50b0be9 [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.
David Riley668a1492017-02-22 15:31:30 -0800102 @property is_special Boolean indicating if the event is a special task.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700103
104 """
105
106 get_config_value = global_config.global_config.get_config_value
107 _LOG_URL_PATTERN = get_config_value('CROS', 'log_url_pattern')
108
109 @classmethod
110 def get_log_url(cls, afe_hostname, logdir):
111 """Return a URL to job results.
112
113 The URL is constructed from a base URL determined by the
114 global config, plus the relative path of the job's log
115 directory.
116
117 @param afe_hostname Hostname for autotest frontend
118 @param logdir Relative path of the results log directory.
119
120 @return A URL to the requested results log.
121
122 """
123 return cls._LOG_URL_PATTERN % (afe_hostname, logdir)
124
125
126 def __init__(self, start_time, end_time):
127 self.start_time = parse_time(start_time)
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700128 self.end_time = parse_time(end_time)
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700129
130
131 def __cmp__(self, other):
132 """Compare two jobs by their start time.
133
134 This is a standard Python `__cmp__` method to allow sorting
135 `_JobEvent` objects by their times.
136
137 @param other The `_JobEvent` object to compare to `self`.
138
139 """
140 return self.start_time - other.start_time
141
142
143 @property
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -0800144 def id(self):
145 """Return the id of the event in the AFE database."""
146 raise NotImplemented()
147
148
149 @property
150 def name(self):
151 """Return the name of the event."""
152 raise NotImplemented()
153
154
155 @property
156 def job_status(self):
157 """Return a short string describing the event's final status."""
158 raise NotImplemented()
159
160
161 @property
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700162 def job_url(self):
163 """Return the URL for this event's job logs."""
164 raise NotImplemented()
165
166
167 @property
168 def diagnosis(self):
169 """Return the status of the DUT after this event.
170
171 The diagnosis is interpreted as follows:
172 UNKNOWN - The DUT status was the same before and after
173 the event.
174 WORKING - The DUT appeared to be working after the event.
175 BROKEN - The DUT likely required manual intervention
176 after the event.
177
178 @return A valid diagnosis value.
179
180 """
181 raise NotImplemented()
182
183
David Riley668a1492017-02-22 15:31:30 -0800184 @property
185 def is_special(self):
186 """Return if the event is for a special task."""
187 raise NotImplemented()
188
189
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700190class _SpecialTaskEvent(_JobEvent):
191 """`_JobEvent` adapter for special tasks.
192
193 This class wraps the standard `_JobEvent` interface around a row
194 in the `afe_special_tasks` table.
195
196 """
197
198 @classmethod
199 def get_tasks(cls, afe, host_id, start_time, end_time):
200 """Return special tasks for a host in a given time range.
201
202 Return a list of `_SpecialTaskEvent` objects representing all
203 special tasks that ran on the given host in the given time
204 range. The list is ordered as it was returned by the query
205 (i.e. unordered).
206
207 @param afe Autotest frontend
208 @param host_id Database host id of the desired host.
209 @param start_time Start time of the range of interest.
210 @param end_time End time of the range of interest.
211
212 @return A list of `_SpecialTaskEvent` objects.
213
214 """
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700215 query_start = time_utils.epoch_time_to_date_string(start_time)
216 query_end = time_utils.epoch_time_to_date_string(end_time)
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700217 tasks = afe.get_host_special_tasks(
218 host_id,
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700219 time_started__gte=query_start,
220 time_finished__lte=query_end,
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700221 is_complete=1)
222 return [cls(afe.server, t) for t in tasks]
223
224
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700225 @classmethod
226 def get_status_task(cls, afe, host_id, end_time):
227 """Return the task indicating a host's status at a given time.
228
229 The task returned determines the status of the DUT; the
230 diagnosis on the task indicates the diagnosis for the DUT at
231 the given `end_time`.
232
233 @param afe Autotest frontend
234 @param host_id Database host id of the desired host.
235 @param end_time Find status as of this time.
236
237 @return A `_SpecialTaskEvent` object for the requested task,
238 or `None` if no task was found.
239
240 """
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700241 query_end = time_utils.epoch_time_to_date_string(end_time)
242 task = afe.get_host_status_task(host_id, query_end)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700243 return cls(afe.server, task) if task else None
244
245
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700246 def __init__(self, afe_hostname, afetask):
247 self._afe_hostname = afe_hostname
248 self._afetask = afetask
249 super(_SpecialTaskEvent, self).__init__(
250 afetask.time_started, afetask.time_finished)
251
252
253 @property
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -0800254 def id(self):
255 return self._afetask.id
256
257
258 @property
259 def name(self):
260 return self._afetask.task
261
262
263 @property
264 def job_status(self):
265 if self._afetask.is_aborted:
266 return 'ABORTED'
267 elif self._afetask.success:
268 return 'PASS'
269 else:
270 return 'FAIL'
271
272
273 @property
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700274 def job_url(self):
275 logdir = ('hosts/%s/%s-%s' %
276 (self._afetask.host.hostname, self._afetask.id,
277 self._afetask.task.lower()))
278 return _SpecialTaskEvent.get_log_url(self._afe_hostname, logdir)
279
280
281 @property
282 def diagnosis(self):
283 if self._afetask.success:
284 return WORKING
285 elif self._afetask.task == 'Repair':
286 return BROKEN
287 else:
288 return UNKNOWN
289
290
David Riley668a1492017-02-22 15:31:30 -0800291 @property
292 def is_special(self):
293 return True
294
295
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700296class _TestJobEvent(_JobEvent):
297 """`_JobEvent` adapter for regular test jobs.
298
299 This class wraps the standard `_JobEvent` interface around a row
300 in the `afe_host_queue_entries` table.
301
302 """
303
304 @classmethod
305 def get_hqes(cls, afe, host_id, start_time, end_time):
306 """Return HQEs for a host in a given time range.
307
308 Return a list of `_TestJobEvent` objects representing all the
309 HQEs of all the jobs that ran on the given host in the given
310 time range. The list is ordered as it was returned by the
311 query (i.e. unordered).
312
313 @param afe Autotest frontend
314 @param host_id Database host id of the desired host.
315 @param start_time Start time of the range of interest.
316 @param end_time End time of the range of interest.
317
318 @return A list of `_TestJobEvent` objects.
319
320 """
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700321 query_start = time_utils.epoch_time_to_date_string(start_time)
322 query_end = time_utils.epoch_time_to_date_string(end_time)
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700323 hqelist = afe.get_host_queue_entries(
324 host_id=host_id,
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700325 start_time=query_start,
326 end_time=query_end,
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700327 complete=1)
328 return [cls(afe.server, hqe) for hqe in hqelist]
329
330
331 def __init__(self, afe_hostname, hqe):
332 self._afe_hostname = afe_hostname
333 self._hqe = hqe
334 super(_TestJobEvent, self).__init__(
335 hqe.started_on, hqe.finished_on)
336
337
338 @property
J. Richard Barnetteaf1e8262016-03-04 12:55:11 -0800339 def id(self):
340 return self._hqe.id
341
342
343 @property
344 def name(self):
345 return self._hqe.job.name
346
347
348 @property
349 def job_status(self):
350 return self._hqe.status
351
352
353 @property
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700354 def job_url(self):
355 logdir = '%s-%s' % (self._hqe.job.id, self._hqe.job.owner)
356 return _TestJobEvent.get_log_url(self._afe_hostname, logdir)
357
358
359 @property
360 def diagnosis(self):
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700361 return UNKNOWN
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700362
363
David Riley668a1492017-02-22 15:31:30 -0800364 @property
365 def is_special(self):
366 return False
367
368
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700369class HostJobHistory(object):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700370 """Class to query and remember DUT execution and status history.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700371
372 This class is responsible for querying the database to determine
373 the history of a single DUT in a time interval of interest, and
374 for remembering the query results for reporting.
375
376 @property hostname Host name of the DUT.
David Rileyd23d24a2016-10-04 20:07:00 -0700377 @property start_time Start of the requested time interval, as a unix
378 timestamp (epoch time).
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700379 This field may be `None`.
David Rileyd23d24a2016-10-04 20:07:00 -0700380 @property end_time End of the requested time interval, as a unix
381 timestamp (epoch time).
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700382 @property _afe Autotest frontend for queries.
383 @property _host Database host object for the DUT.
384 @property _history A list of jobs and special tasks that
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700385 ran on the DUT in the requested time
386 interval, ordered in reverse, from latest
387 to earliest.
388
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700389 @property _status_interval A list of all the jobs and special
390 tasks that ran on the DUT in the
391 last diagnosis interval prior to
392 `end_time`, ordered from latest to
393 earliest.
394 @property _status_diagnosis The DUT's status as of `end_time`.
395 @property _status_task The DUT's last status task as of
396 `end_time`.
397
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700398 """
399
400 @classmethod
401 def get_host_history(cls, afe, hostname, start_time, end_time):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700402 """Create a `HostJobHistory` instance for a single host.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700403
404 Simple factory method to construct host history from a
405 hostname. Simply looks up the host in the AFE database, and
406 passes it to the class constructor.
407
408 @param afe Autotest frontend
409 @param hostname Name of the host.
410 @param start_time Start time for the history's time
411 interval.
412 @param end_time End time for the history's time interval.
413
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700414 @return A new `HostJobHistory` instance.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700415
416 """
417 afehost = afe.get_hosts(hostname=hostname)[0]
418 return cls(afe, afehost, start_time, end_time)
419
420
421 @classmethod
422 def get_multiple_histories(cls, afe, start_time, end_time,
423 board=None, pool=None):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700424 """Create `HostJobHistory` instances for a set of hosts.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700425
426 The set of hosts can be specified as "all hosts of a given
427 board type", "all hosts in a given pool", or "all hosts
428 of a given board and pool".
429
430 @param afe Autotest frontend
431 @param start_time Start time for the history's time
432 interval.
433 @param end_time End time for the history's time interval.
434 @param board All hosts must have this board type; if
435 `None`, all boards are allowed.
436 @param pool All hosts must be in this pool; if
437 `None`, all pools are allowed.
438
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700439 @return A list of new `HostJobHistory` instances.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700440
441 """
442 # If `board` or `pool` are both `None`, we could search the
443 # entire database, which is more expensive than we want.
444 # Our caller currently won't (can't) do this, but assert to
445 # be safe.
446 assert board is not None or pool is not None
447 labels = []
448 if board is not None:
449 labels.append(constants.Labels.BOARD_PREFIX + board)
450 if pool is not None:
451 labels.append(constants.Labels.POOL_PREFIX + pool)
452 kwargs = {'multiple_labels': labels}
453 hosts = afe.get_hosts(**kwargs)
454 return [cls(afe, h, start_time, end_time) for h in hosts]
455
456
457 def __init__(self, afe, afehost, start_time, end_time):
458 self._afe = afe
459 self.hostname = afehost.hostname
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700460 self.end_time = end_time
461 self.start_time = start_time
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700462 self._host = afehost
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700463 # Don't spend time on queries until they're needed.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700464 self._history = None
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700465 self._status_interval = None
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700466 self._status_diagnosis = None
467 self._status_task = None
468
469
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700470 def _get_history(self, start_time, end_time):
471 """Get the list of events for the given interval."""
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700472 newtasks = _SpecialTaskEvent.get_tasks(
473 self._afe, self._host.id, start_time, end_time)
474 newhqes = _TestJobEvent.get_hqes(
475 self._afe, self._host.id, start_time, end_time)
476 newhistory = newtasks + newhqes
477 newhistory.sort(reverse=True)
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700478 return newhistory
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700479
480
481 def __iter__(self):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700482 if self._history is None:
483 self._history = self._get_history(self.start_time,
484 self.end_time)
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700485 return self._history.__iter__()
486
487
J. Richard Barnette96db3492015-03-27 17:23:52 -0700488 def _extract_prefixed_label(self, prefix):
J. Richard Barnette6948ed32015-05-06 08:57:10 -0700489 labels = [l for l in self._host.labels
490 if l.startswith(prefix)]
491 return labels[0][len(prefix) : ] if labels else None
J. Richard Barnette96db3492015-03-27 17:23:52 -0700492
493
J. Richard Barnette3d0590a2015-04-29 12:56:12 -0700494 @property
495 def host(self):
496 """Return the AFE host object for this history."""
497 return self._host
498
499
500 @property
501 def host_board(self):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700502 """Return the board name for this history's DUT."""
503 prefix = constants.Labels.BOARD_PREFIX
504 return self._extract_prefixed_label(prefix)
505
506
J. Richard Barnette3d0590a2015-04-29 12:56:12 -0700507 @property
508 def host_pool(self):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700509 """Return the pool name for this history's DUT."""
510 prefix = constants.Labels.POOL_PREFIX
511 return self._extract_prefixed_label(prefix)
512
513
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700514 def _init_status_task(self):
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700515 """Fill in `self._status_diagnosis` and `_status_task`."""
516 if self._status_diagnosis is not None:
517 return
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700518 self._status_task = _SpecialTaskEvent.get_status_task(
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700519 self._afe, self._host.id, self.end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700520 if self._status_task is not None:
521 self._status_diagnosis = self._status_task.diagnosis
522 else:
523 self._status_diagnosis = UNKNOWN
524
525
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700526 def _init_status_interval(self):
527 """Fill in `self._status_interval`."""
528 if self._status_interval is not None:
529 return
530 self._init_status_task()
531 self._status_interval = []
532 if self._status_task is None:
533 return
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700534 query_end = time_utils.epoch_time_to_date_string(self.end_time)
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700535 interval = self._afe.get_host_diagnosis_interval(
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700536 self._host.id, query_end,
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700537 self._status_diagnosis != WORKING)
538 if not interval:
539 return
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700540 self._status_interval = self._get_history(
541 parse_time(interval[0]),
542 parse_time(interval[1]))
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700543
544
545 def diagnosis_interval(self):
546 """Find this history's most recent diagnosis interval.
547
548 Returns a list of `_JobEvent` instances corresponding to the
549 most recent diagnosis interval occurring before this
550 history's end time.
551
552 The list is returned as with `self._history`, ordered from
553 most to least recent.
554
555 @return The list of the `_JobEvent`s in the diagnosis
556 interval.
557
558 """
559 self._init_status_interval()
560 return self._status_interval
561
562
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700563 def last_diagnosis(self):
564 """Return the diagnosis of whether the DUT is working.
565
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700566 This searches the DUT's job history, looking for the most
567 recent status task for the DUT. Return a tuple of
568 `(diagnosis, task)`.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700569
570 The `diagnosis` entry in the tuple is one of these values:
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700571 * UNUSED - The host's last status task is older than
572 `self.start_time`.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700573 * WORKING - The DUT is working.
574 * BROKEN - The DUT likely requires manual intervention.
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700575 * UNKNOWN - No task could be found indicating status for
576 the DUT.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700577
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700578 If the DUT was working at last check, but hasn't been used
579 inside this history's time interval, the status `UNUSED` is
580 returned with the last status task, instead of `WORKING`.
581
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700582 The `task` entry in the tuple is the status task that led to
583 the diagnosis. The task will be `None` if the diagnosis is
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700584 `UNKNOWN`.
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700585
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700586 @return A tuple with the DUT's diagnosis and the task that
J. Richard Barnettefa27f5b2015-03-23 17:11:54 -0700587 determined it.
588
589 """
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700590 self._init_status_task()
J. Richard Barnette2c9b1132015-07-10 10:47:28 -0700591 diagnosis = self._status_diagnosis
592 if (self.start_time is not None and
593 self._status_task is not None and
594 self._status_task.end_time < self.start_time and
595 diagnosis == WORKING):
596 diagnosis = UNUSED
597 return diagnosis, self._status_task
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700598
599
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700600def get_diagnosis_interval(host_id, end_time, success):
601 """Return the last diagnosis interval for a given host and time.
602
603 This routine queries the database for the special tasks on a
604 given host before a given time. From those tasks it selects the
605 last status task before a change in status, and the first status
606 task after the change. When `success` is true, the change must
607 be from "working" to "broken". When false, the search is for a
608 change in the opposite direction.
609
610 A "successful status task" is any successful special task. A
611 "failed status task" is a failed Repair task. These criteria
612 are based on the definition of "status task" in the module-level
613 docstring, above.
614
615 This is the RPC endpoint for `AFE.get_host_diagnosis_interval()`.
616
617 @param host_id Database host id of the desired host.
618 @param end_time Find the last eligible interval before this time.
619 @param success Whether the eligible interval should start with a
620 success or a failure.
621
622 @return A list containing the start time of the earliest job
623 selected, and the end time of the latest job.
624
625 """
626 base_query = afe_models.SpecialTask.objects.filter(
627 host_id=host_id, is_complete=True)
628 success_query = base_query.filter(success=True)
629 failure_query = base_query.filter(success=False, task='Repair')
630 if success:
631 query0 = success_query
632 query1 = failure_query
633 else:
634 query0 = failure_query
635 query1 = success_query
636 query0 = query0.filter(time_finished__lte=end_time)
637 query0 = query0.order_by('time_started').reverse()
638 if not query0:
639 return []
640 task0 = query0[0]
641 query1 = query1.filter(time_finished__gt=task0.time_finished)
642 task1 = query1.order_by('time_started')[0]
643 return [task0.time_started.strftime(time_utils.TIME_FMT),
644 task1.time_finished.strftime(time_utils.TIME_FMT)]
645
646
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700647def get_status_task(host_id, end_time):
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700648 """Get the last status task for a host before a given time.
649
650 This routine returns a Django query for the AFE database to find
651 the last task that finished on the given host before the given
652 time that was either a successful task, or a Repair task. The
653 query criteria are based on the definition of "status task" in
654 the module-level docstring, above.
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700655
656 This is the RPC endpoint for `_SpecialTaskEvent.get_status_task()`.
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700657
658 @param host_id Database host id of the desired host.
659 @param end_time End time of the range of interest.
660
661 @return A Django query-set selecting the single special task of
662 interest.
663
664 """
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700665 # Selects status tasks: any Repair task, or any successful task.
666 status_tasks = (django_models.Q(task='Repair') |
667 django_models.Q(success=True))
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700668 # Our caller needs a Django query set in order to serialize the
669 # result, so we don't resolve the query here; we just return a
670 # slice with at most one element.
671 return afe_models.SpecialTask.objects.filter(
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700672 status_tasks,
J. Richard Barnette39255fa2015-04-14 17:23:41 -0700673 host_id=host_id,
674 time_finished__lte=end_time,
675 is_complete=True).order_by('time_started').reverse()[0:1]