1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2015 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li"""Sequence extensions to server_job. 7*9c5db199SXin LiAdds ability to schedule jobs on given machines. 8*9c5db199SXin Li""" 9*9c5db199SXin Li 10*9c5db199SXin Lifrom __future__ import absolute_import 11*9c5db199SXin Lifrom __future__ import division 12*9c5db199SXin Lifrom __future__ import print_function 13*9c5db199SXin Li 14*9c5db199SXin Liimport logging 15*9c5db199SXin Liimport os 16*9c5db199SXin Li 17*9c5db199SXin Liimport common 18*9c5db199SXin Lifrom autotest_lib.client.common_lib import control_data 19*9c5db199SXin Lifrom autotest_lib.client.common_lib import priorities 20*9c5db199SXin Lifrom autotest_lib.server import utils 21*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import control_file_getter 22*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import frontend_wrappers 23*9c5db199SXin Lifrom autotest_lib.site_utils import job_directories 24*9c5db199SXin Liimport six 25*9c5db199SXin Lifrom six.moves import range 26*9c5db199SXin Li 27*9c5db199SXin LiMINUTE_IN_SECS = 60 28*9c5db199SXin LiHOUR_IN_MINUTES = 60 29*9c5db199SXin LiHOUR_IN_SECS = HOUR_IN_MINUTES * MINUTE_IN_SECS 30*9c5db199SXin LiDAY_IN_HOURS = 24 31*9c5db199SXin LiDAY_IN_SECS = DAY_IN_HOURS*HOUR_IN_SECS 32*9c5db199SXin Li 33*9c5db199SXin LiDEFAULT_JOB_TIMEOUT_IN_MINS = 4 * HOUR_IN_MINUTES 34*9c5db199SXin Li 35*9c5db199SXin Liclass SequenceJob(object): 36*9c5db199SXin Li """Define part of a sequence that will be scheduled by the sequence test.""" 37*9c5db199SXin Li 38*9c5db199SXin Li CONTROL_FILE = """ 39*9c5db199SXin Lidef run(machine): 40*9c5db199SXin Li job.run_test('%s', host=hosts.create_host(machine), client_ip=machine%s) 41*9c5db199SXin Li 42*9c5db199SXin Liparallel_simple(run, machines) 43*9c5db199SXin Li""" 44*9c5db199SXin Li 45*9c5db199SXin Li def __init__(self, name, args=None, iteration=1, duration=None, 46*9c5db199SXin Li fetch_control_file=False): 47*9c5db199SXin Li """ 48*9c5db199SXin Li Constructor 49*9c5db199SXin Li 50*9c5db199SXin Li @param name: name of the server test to run. 51*9c5db199SXin Li @param args: arguments needed by the server test. 52*9c5db199SXin Li @param iteration: number of copy of this test to sechudle 53*9c5db199SXin Li @param duration: expected duration of the test (in seconds). 54*9c5db199SXin Li @param fetch_control_file: If True, fetch the control file contents 55*9c5db199SXin Li from disk. Otherwise uses the template 56*9c5db199SXin Li control file. 57*9c5db199SXin Li """ 58*9c5db199SXin Li self._name = name 59*9c5db199SXin Li self._args = args or {} 60*9c5db199SXin Li self._iteration = iteration 61*9c5db199SXin Li self._duration = duration 62*9c5db199SXin Li self._fetch_control_file = fetch_control_file 63*9c5db199SXin Li 64*9c5db199SXin Li 65*9c5db199SXin Li def child_job_name(self, machine, iteration_number): 66*9c5db199SXin Li """ 67*9c5db199SXin Li Return a name for a child job. 68*9c5db199SXin Li 69*9c5db199SXin Li @param machine: machine name on which the test will run. 70*9c5db199SXin Li @param iteration_number: number with 0 and self._iteration - 1. 71*9c5db199SXin Li 72*9c5db199SXin Li @returns a unique name based on the machine, the name and the iteration. 73*9c5db199SXin Li """ 74*9c5db199SXin Li name_parts = [machine, self._name] 75*9c5db199SXin Li tag = self._args.get('tag') 76*9c5db199SXin Li if tag: 77*9c5db199SXin Li name_parts.append(tag) 78*9c5db199SXin Li if self._iteration > 1: 79*9c5db199SXin Li name_parts.append(str(iteration_number)) 80*9c5db199SXin Li return '_'.join(name_parts) 81*9c5db199SXin Li 82*9c5db199SXin Li 83*9c5db199SXin Li def child_job_timeout(self): 84*9c5db199SXin Li """ 85*9c5db199SXin Li Get the child job timeout in minutes. 86*9c5db199SXin Li 87*9c5db199SXin Li @param args: arguments sent to the test. 88*9c5db199SXin Li 89*9c5db199SXin Li @returns a timeout value for the test, 4h by default. 90*9c5db199SXin Li """ 91*9c5db199SXin Li if self._duration: 92*9c5db199SXin Li return 2 * int(self._duration) // MINUTE_IN_SECS 93*9c5db199SXin Li # default value: 94*9c5db199SXin Li return DEFAULT_JOB_TIMEOUT_IN_MINS 95*9c5db199SXin Li 96*9c5db199SXin Li 97*9c5db199SXin Li def child_control_file(self): 98*9c5db199SXin Li """ 99*9c5db199SXin Li Generate the child job's control file. 100*9c5db199SXin Li 101*9c5db199SXin Li If not fetching the contents, use the template control file and 102*9c5db199SXin Li populate the template control file with the test name and expand the 103*9c5db199SXin Li arguments list. 104*9c5db199SXin Li 105*9c5db199SXin Li @param test: name of the test to run 106*9c5db199SXin Li @param args: dictionary of argument for this test. 107*9c5db199SXin Li 108*9c5db199SXin Li @returns a fully built control file to be use for the child job. 109*9c5db199SXin Li """ 110*9c5db199SXin Li if self._fetch_control_file: 111*9c5db199SXin Li # TODO (sbasi): Add arg support. 112*9c5db199SXin Li cntl_file_getter = control_file_getter.FileSystemGetter( 113*9c5db199SXin Li [os.path.join(os.path.dirname(os.path.realpath(__file__)), 114*9c5db199SXin Li '..')]) 115*9c5db199SXin Li return cntl_file_getter.get_control_file_contents_by_name( 116*9c5db199SXin Li self._name) 117*9c5db199SXin Li child_args = ['',] 118*9c5db199SXin Li for arg, value in six.iteritems(self._args): 119*9c5db199SXin Li child_args.append('%s=%s' % (arg, repr(value))) 120*9c5db199SXin Li if self._duration: 121*9c5db199SXin Li child_args.append('duration=%d' % self._duration) 122*9c5db199SXin Li return self.CONTROL_FILE % (self._name, ', '.join(child_args)) 123*9c5db199SXin Li 124*9c5db199SXin Li 125*9c5db199SXin Li def schedule(self, job, timeout_mins, machine): 126*9c5db199SXin Li """ 127*9c5db199SXin Li Sequence a job on the running AFE. 128*9c5db199SXin Li 129*9c5db199SXin Li Will schedule a given test on the job machine(s). 130*9c5db199SXin Li Support a subset of tests: 131*9c5db199SXin Li - server job 132*9c5db199SXin Li - no hostless. 133*9c5db199SXin Li - no cleanup around tests. 134*9c5db199SXin Li 135*9c5db199SXin Li @param job: server_job object that will server as parent. 136*9c5db199SXin Li @param timeout_mins: timeout to set up: if the test last more than 137*9c5db199SXin Li timeout_mins, the test will fail. 138*9c5db199SXin Li @param machine: machine to run the test on. 139*9c5db199SXin Li 140*9c5db199SXin Li @returns a maximal time in minutes that the sequence can take. 141*9c5db199SXin Li """ 142*9c5db199SXin Li afe = frontend_wrappers.RetryingAFE(timeout_min=30, delay_sec=10, 143*9c5db199SXin Li user=job.user, debug=False) 144*9c5db199SXin Li # job_directores.get_job_id_or_task_id() will return a non-int opaque id 145*9c5db199SXin Li # for ChromeOS Skylab tasks. But sequences will break in that case 146*9c5db199SXin Li # anyway, because they try to create AFE jobs internally. 147*9c5db199SXin Li current_job_id = int( 148*9c5db199SXin Li job_directories.get_job_id_or_task_id(job.resultdir)) 149*9c5db199SXin Li logging.debug('Current job id: %s', current_job_id) 150*9c5db199SXin Li runtime_mins = self.child_job_timeout() 151*9c5db199SXin Li hostname = utils.get_hostname_from_machine(machine) 152*9c5db199SXin Li 153*9c5db199SXin Li for i in range(0, self._iteration): 154*9c5db199SXin Li child_job_name = self.child_job_name(hostname, i) 155*9c5db199SXin Li logging.debug('Creating job: %s', child_job_name) 156*9c5db199SXin Li afe.create_job( 157*9c5db199SXin Li self.child_control_file(), 158*9c5db199SXin Li name=child_job_name, 159*9c5db199SXin Li priority=priorities.Priority.DEFAULT, 160*9c5db199SXin Li control_type=control_data.CONTROL_TYPE.SERVER, 161*9c5db199SXin Li hosts=[hostname], meta_hosts=(), one_time_hosts=(), 162*9c5db199SXin Li synch_count=None, is_template=False, 163*9c5db199SXin Li timeout_mins=timeout_mins + (i + 1) * runtime_mins, 164*9c5db199SXin Li max_runtime_mins=runtime_mins, 165*9c5db199SXin Li run_verify=False, email_list='', dependencies=(), 166*9c5db199SXin Li reboot_before=None, reboot_after=None, 167*9c5db199SXin Li parse_failed_repair=None, 168*9c5db199SXin Li hostless=False, keyvals=None, 169*9c5db199SXin Li drone_set=None, image=None, 170*9c5db199SXin Li parent_job_id=current_job_id, test_retry=0, run_reset=False, 171*9c5db199SXin Li require_ssp=utils.is_in_container()) 172*9c5db199SXin Li return runtime_mins * self._iteration 173*9c5db199SXin Li 174*9c5db199SXin Li 175*9c5db199SXin Lidef sequence_schedule(job, machines, server_tests): 176*9c5db199SXin Li """ 177*9c5db199SXin Li Schedule the tests to run 178*9c5db199SXin Li 179*9c5db199SXin Li Launch all the tests in the sequence on all machines. 180*9c5db199SXin Li Returns as soon as the jobs are launched. 181*9c5db199SXin Li 182*9c5db199SXin Li @param job: Job running. 183*9c5db199SXin Li @param machines: machine to run on. 184*9c5db199SXin Li @param server_tests: Array of sequence_test objects. 185*9c5db199SXin Li """ 186*9c5db199SXin Li for machine in machines: 187*9c5db199SXin Li timeout_mins = 0 188*9c5db199SXin Li for test in server_tests: 189*9c5db199SXin Li timeout_mins += test.schedule(job, timeout_mins, machine) 190