• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 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"""Extra functions for frontend.afe.models.Job objects.
6
7Most of these exist in tightly coupled forms in legacy Autotest code
8(e.g., part of methods with completely unrelated names on Task objects
9under multiple layers of abstract classes).  These are defined here to
10sanely reuse without having to commit to a long refactor of legacy code
11that is getting deleted soon.
12
13It's not really a good idea to define these on the Job class either;
14they are specialized and the Job class already suffers from method
15bloat.
16"""
17
18from __future__ import absolute_import
19from __future__ import division
20from __future__ import print_function
21
22import os
23import time
24import urllib
25
26from lucifer import autotest
27from lucifer import results
28
29
30def is_hostless(job):
31    """Return True if the job is hostless.
32
33    @param job: frontend.afe.models.Job instance
34    """
35    return not hostnames(job)
36
37
38def hostnames(job):
39    """Return a list of hostnames for a job.
40
41    @param job: frontend.afe.models.Job instance
42    """
43    hqes = job.hostqueueentry_set.all().prefetch_related('host')
44    return [hqe.host.hostname for hqe in hqes if hqe.host is not None]
45
46
47def is_aborted(job):
48    """Return if the job is aborted.
49
50    (This means the job is marked for abortion; the job can still be
51    running.)
52
53    @param job: frontend.afe.models.Job instance
54    """
55    return job.hostqueueentry_set.filter(aborted=True).exists()
56
57
58def is_server_job(job):
59    """Return whether the job is a server job.
60
61    @param job: frontend.afe.models.Job instance
62    """
63    return not is_client_job(job)
64
65
66def is_client_job(job):
67    """Return whether the job is a client job.
68
69    If the job is not a client job, it is a server job.
70
71    (In theory a job can be neither.  I have no idea what you should do
72    in that case.)
73
74    @param job: frontend.afe.models.Job instance
75    """
76    CONTROL_TYPE = autotest.load('client.common_lib.control_data').CONTROL_TYPE
77    return CONTROL_TYPE.get_value(job.control_type) == CONTROL_TYPE.CLIENT
78
79
80def needs_ssp(job):
81    """Return whether the job needs SSP.
82
83    This also looks up the config for jobs that do not have a value
84    specified.
85
86    @param job: frontend.afe.models.Job instance
87    """
88    return (_ssp_enabled()
89            and is_server_job(job)
90            # None is the same as True.
91            and job.require_ssp != False)
92
93
94def _ssp_enabled():
95    """Return whether SSP is enabled in the config."""
96    global_config = autotest.load('client.common_lib.global_config')
97    return global_config.global_config.get_config_value(
98            'AUTOSERV', 'enable_ssp_container', type=bool,
99            default=True)
100
101
102def control_file_path(workdir):
103    """Path to control file for a job.
104
105    This makes more sense in the old Autotest drone world.  The
106    scheduler has to copy the control file to the drone.  It goes to a
107    temporary path `drone_tmp/attach.N`.
108
109    The drone is then able to run `autoserv <args> drone_tmp/attach.N`.
110
111    But in the Lucifer world, we are already running on the drone, so we
112    don't need to rendezvous with a temporary directory through
113    drone_manager first.
114
115    So pick an arbitrary filename to plop into the workdir.  autoserv
116    will later copy this back to the standard control/control.srv.
117    """
118    return os.path.join(workdir, 'lucifer', 'control_attach')
119
120
121def prepare_control_file(job, workdir):
122    """Prepare control file for a job."""
123    with open(control_file_path(workdir), 'w') as f:
124        f.write(job.control_file)
125
126
127def prepare_keyvals_files(job, workdir):
128    """Prepare Autotest keyvals files for a job."""
129    keyvals = job.keyval_dict()
130    keyvals['job_queued'] = \
131            int(time.mktime(job.created_on.timetuple()))
132    results.write_keyvals(workdir, keyvals)
133    if is_hostless(job):
134        return
135    for hqe in job.hostqueueentry_set.all().prefetch_related('host'):
136        results.write_host_keyvals(
137                workdir, hqe.host.hostname, _host_keyvals(hqe.host))
138
139
140def write_aborted_keyvals_and_status(job, workdir):
141    """Write the keyvals and status for an aborted job."""
142    aborted_by = 'autotest_system'
143    aborted_on = int(time.time())
144    for hqe in job.hostqueueentry_set.all():
145        if not hasattr(hqe, 'abortedhostqueueentry'):
146            continue
147        ahqe = hqe.abortedhostqueueentry
148        aborted_by = ahqe.aborted_by
149        aborted_on = int(time.mktime(ahqe.aborted_on.timetuple()))
150        break
151    results.write_keyvals(workdir, {
152            'aborted_by': aborted_by,
153            'aborted_on': aborted_on,
154    })
155    results.write_status_comment(
156            workdir, 'Job aborted by %s on %s' % (aborted_by, aborted_on))
157
158
159def _host_keyvals(host):
160    """Return keyvals dict for a host.
161
162    @param host: frontend.afe.models.Host instance
163    """
164    labels = list(_host_labels(host))
165    platform = None
166    for label in labels:
167        if label.platform:
168            platform = label.name
169    return {
170            'platform': platform,
171            'labels': ','.join(urllib.quote(label.name) for label in labels),
172    }
173
174
175def _host_labels(host):
176    """Return an iterable of labels for a host.
177
178    @param host: frontend.afe.models.Host instance
179    """
180    if autotest.load('scheduler.scheduler_models').RESPECT_STATIC_LABELS:
181        return _host_labels_with_static(host)
182    else:
183        return host.labels.all()
184
185
186def _host_labels_with_static(host):
187    """Return a generator of labels for a host, respecting static labels.
188
189    @param host: frontend.afe.models.Host instance
190    """
191    models = autotest.load('frontend.afe.models')
192    replaced_label_ids = frozenset(models.ReplacedLabel.objects.all()
193                                   .values_list('label_id', flat=True))
194    shadowed_labels = set()
195    for label in host.labels.all():
196        if label.id in replaced_label_ids:
197            shadowed_labels.add(label.name)
198        else:
199            yield label
200    for label in host.static_labels.all():
201        if label.name in shadowed_labels:
202            yield label
203