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