1# Copyright 2018 The Chromium 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
5"""Library for frontend.afe.models.JobHandoff and job cleanup."""
6
7from __future__ import absolute_import
8from __future__ import division
9from __future__ import print_function
10
11import datetime
12import logging
13import socket
14
15from lucifer import autotest
16
17logger = logging.getLogger(__name__)
18
19
20_JOB_GRACE_SECS = 10
21
22
23def incomplete():
24    """Return a QuerySet of incomplete JobHandoffs.
25
26    JobHandoff created within a cutoff period are exempt to allow the
27    job the chance to acquire its lease file; otherwise, incomplete jobs
28    without an active lease are considered dead.
29
30    @returns: Django QuerySet
31    """
32    models = autotest.load('frontend.afe.models')
33    Q = autotest.deps_load('django.db.models').Q
34    # Time ---*---------|---------*-------|--->
35    #    incomplete   cutoff   newborn   now
36    cutoff = (datetime.datetime.now()
37              - datetime.timedelta(seconds=_JOB_GRACE_SECS))
38    return (models.JobHandoff.objects
39            .filter(completed=False, created__lt=cutoff)
40            .filter(Q(drone=socket.gethostname()) | Q(drone=None)))
41
42
43def clean_up(job_ids):
44    """Clean up failed jobs failed in database.
45
46    This brings the database into a clean state, which includes marking
47    the job, HQEs, and hosts.
48    """
49    if not job_ids:
50        return
51    models = autotest.load('frontend.afe.models')
52    logger.info('Cleaning up failed jobs: %r', job_ids)
53    hqes = models.HostQueueEntry.objects.filter(job_id__in=job_ids)
54    logger.debug('Cleaning up HQEs: %r', hqes.values_list('id', flat=True))
55    _clean_up_hqes(hqes)
56    host_ids = {id for id in hqes.values_list('host_id', flat=True)
57                if id is not None}
58    logger.debug('Found Hosts associated with jobs: %r', host_ids)
59    _clean_up_hosts(host_ids)
60
61
62def _clean_up_hqes(hqes):
63    models = autotest.load('frontend.afe.models')
64    logger.debug('Cleaning up HQEs: %r', hqes.values_list('id', flat=True))
65    hqes.update(complete=True,
66                active=False,
67                status=models.HostQueueEntry.Status.FAILED)
68    (hqes.exclude(started_on=None)
69     .update(finished_on=datetime.datetime.now()))
70
71
72def _clean_up_hosts(host_ids):
73    models = autotest.load('frontend.afe.models')
74    transaction = autotest.deps_load('django.db.transaction')
75    with transaction.commit_on_success():
76        active_hosts = {
77            id for id in (models.HostQueueEntry.objects
78                          .filter(active=True, complete=False)
79                          .values_list('host_id', flat=True))
80            if id is not None}
81        logger.debug('Found active Hosts: %r', active_hosts)
82        (models.Host.objects
83         .filter(id__in=host_ids)
84         .exclude(id__in=active_hosts)
85         .update(status=None))
86
87
88def mark_complete(job_ids):
89    """Mark the corresponding JobHandoffs as completed."""
90    if not job_ids:
91        return
92    models = autotest.load('frontend.afe.models')
93    logger.info('Marking job handoffs complete: %r', job_ids)
94    (models.JobHandoff.objects
95     .filter(job_id__in=job_ids)
96     .update(completed=True))
97