# Copyright 2017 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Extra functions for frontend.afe.models.Job objects. Most of these exist in tightly coupled forms in legacy Autotest code (e.g., part of methods with completely unrelated names on Task objects under multiple layers of abstract classes). These are defined here to sanely reuse without having to commit to a long refactor of legacy code that is getting deleted soon. It's not really a good idea to define these on the Job class either; they are specialized and the Job class already suffers from method bloat. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function import os import time import urllib from lucifer import autotest from lucifer import results def is_hostless(job): """Return True if the job is hostless. @param job: frontend.afe.models.Job instance """ return not hostnames(job) def hostnames(job): """Return a list of hostnames for a job. @param job: frontend.afe.models.Job instance """ hqes = job.hostqueueentry_set.all().prefetch_related('host') return [hqe.host.hostname for hqe in hqes if hqe.host is not None] def is_aborted(job): """Return if the job is aborted. (This means the job is marked for abortion; the job can still be running.) @param job: frontend.afe.models.Job instance """ return job.hostqueueentry_set.filter(aborted=True).exists() def is_server_job(job): """Return whether the job is a server job. @param job: frontend.afe.models.Job instance """ return not is_client_job(job) def is_client_job(job): """Return whether the job is a client job. If the job is not a client job, it is a server job. (In theory a job can be neither. I have no idea what you should do in that case.) @param job: frontend.afe.models.Job instance """ CONTROL_TYPE = autotest.load('client.common_lib.control_data').CONTROL_TYPE return CONTROL_TYPE.get_value(job.control_type) == CONTROL_TYPE.CLIENT def needs_ssp(job): """Return whether the job needs SSP. This also looks up the config for jobs that do not have a value specified. @param job: frontend.afe.models.Job instance """ return (_ssp_enabled() and is_server_job(job) # None is the same as True. and job.require_ssp != False) def _ssp_enabled(): """Return whether SSP is enabled in the config.""" global_config = autotest.load('client.common_lib.global_config') return global_config.global_config.get_config_value( 'AUTOSERV', 'enable_ssp_container', type=bool, default=True) def control_file_path(workdir): """Path to control file for a job. This makes more sense in the old Autotest drone world. The scheduler has to copy the control file to the drone. It goes to a temporary path `drone_tmp/attach.N`. The drone is then able to run `autoserv drone_tmp/attach.N`. But in the Lucifer world, we are already running on the drone, so we don't need to rendezvous with a temporary directory through drone_manager first. So pick an arbitrary filename to plop into the workdir. autoserv will later copy this back to the standard control/control.srv. """ return os.path.join(workdir, 'lucifer', 'control_attach') def prepare_control_file(job, workdir): """Prepare control file for a job.""" with open(control_file_path(workdir), 'w') as f: f.write(job.control_file) def prepare_keyvals_files(job, workdir): """Prepare Autotest keyvals files for a job.""" keyvals = job.keyval_dict() keyvals['job_queued'] = \ int(time.mktime(job.created_on.timetuple())) results.write_keyvals(workdir, keyvals) if is_hostless(job): return for hqe in job.hostqueueentry_set.all().prefetch_related('host'): results.write_host_keyvals( workdir, hqe.host.hostname, _host_keyvals(hqe.host)) def write_aborted_keyvals_and_status(job, workdir): """Write the keyvals and status for an aborted job.""" aborted_by = 'autotest_system' aborted_on = int(time.time()) for hqe in job.hostqueueentry_set.all(): if not hasattr(hqe, 'abortedhostqueueentry'): continue ahqe = hqe.abortedhostqueueentry aborted_by = ahqe.aborted_by aborted_on = int(time.mktime(ahqe.aborted_on.timetuple())) break results.write_keyvals(workdir, { 'aborted_by': aborted_by, 'aborted_on': aborted_on, }) results.write_status_comment( workdir, 'Job aborted by %s on %s' % (aborted_by, aborted_on)) def _host_keyvals(host): """Return keyvals dict for a host. @param host: frontend.afe.models.Host instance """ labels = list(_host_labels(host)) platform = None for label in labels: if label.platform: platform = label.name return { 'platform': platform, 'labels': ','.join(urllib.quote(label.name) for label in labels), } def _host_labels(host): """Return an iterable of labels for a host. @param host: frontend.afe.models.Host instance """ if autotest.load('scheduler.scheduler_models').RESPECT_STATIC_LABELS: return _host_labels_with_static(host) else: return host.labels.all() def _host_labels_with_static(host): """Return a generator of labels for a host, respecting static labels. @param host: frontend.afe.models.Host instance """ models = autotest.load('frontend.afe.models') replaced_label_ids = frozenset(models.ReplacedLabel.objects.all() .values_list('label_id', flat=True)) shadowed_labels = set() for label in host.labels.all(): if label.id in replaced_label_ids: shadowed_labels.add(label.name) else: yield label for label in host.static_labels.all(): if label.name in shadowed_labels: yield label