1# Copyright (c) 2012 The Chromium OS 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 5import datetime 6import logging 7 8import common 9from autotest_lib.client.common_lib import error 10from autotest_lib.server import site_utils 11from autotest_lib.server.cros import provision 12from autotest_lib.server.cros.dynamic_suite import frontend_wrappers, reporting 13 14 15# Number of days back to search for existing job. 16SEARCH_JOB_MAX_DAYS = 14 17 18class DedupingSchedulerException(Exception): 19 """Base class for exceptions from this module.""" 20 pass 21 22 23class ScheduleException(DedupingSchedulerException): 24 """Raised when an error is returned from the AFE during scheduling.""" 25 pass 26 27 28class DedupException(DedupingSchedulerException): 29 """Raised when an error occurs while checking for duplicate jobs.""" 30 pass 31 32 33class DedupingScheduler(object): 34 """A class that will schedule suites to run on a given board, build. 35 36 Includes logic to check whether or not a given (suite, board, build) 37 has already been run. If so, it will skip scheduling that suite. 38 39 @var _afe: a frontend.AFE instance used to talk to autotest. 40 """ 41 42 43 def __init__(self, afe=None, file_bug=False): 44 """Constructor 45 46 @param afe: an instance of AFE as defined in server/frontend.py. 47 Defaults to a frontend_wrappers.RetryingAFE instance. 48 """ 49 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30, 50 delay_sec=10, 51 debug=False) 52 self._file_bug = file_bug 53 54 55 def _ShouldScheduleSuite(self, suite, board, test_source_build): 56 """Return True if |suite| has not yet been run for |build| on |board|. 57 58 True if |suite| has not been run for |build| on |board|, and 59 the lab is open for this particular request. False otherwise. 60 61 @param suite: the name of the suite to run, e.g. 'bvt' 62 @param board: the board to run the suite on, e.g. x86-alex 63 @param test_source_build: Build with the source of tests. 64 65 @return False if the suite was already scheduled, True if not 66 @raise DedupException if the AFE raises while searching for jobs. 67 68 """ 69 try: 70 site_utils.check_lab_status(test_source_build) 71 except site_utils.TestLabException as ex: 72 logging.debug('Skipping suite %s, board %s, build %s: %s', 73 suite, board, test_source_build, str(ex)) 74 return False 75 try: 76 start_time = str(datetime.datetime.now() - 77 datetime.timedelta(days=SEARCH_JOB_MAX_DAYS)) 78 return not self._afe.get_jobs( 79 name__startswith=test_source_build, 80 name__endswith='control.'+suite, 81 created_on__gte=start_time) 82 except Exception as e: 83 raise DedupException(e) 84 85 86 def _Schedule(self, suite, board, build, pool, num, priority, timeout, 87 file_bugs=False, firmware_rw_build=None, 88 test_source_build=None, job_retry=False): 89 """Schedule |suite|, if it hasn't already been run. 90 91 @param suite: the name of the suite to run, e.g. 'bvt' 92 @param board: the board to run the suite on, e.g. x86-alex 93 @param build: the build to install e.g. 94 x86-alex-release/R18-1655.0.0-a1-b1584. 95 @param pool: the pool of machines to use for scheduling purposes. 96 Default: None 97 @param num: the number of devices across which to shard the test suite. 98 Type: integer or None 99 Default: None (uses sharding factor in global_config.ini). 100 @param priority: One of the values from 101 client.common_lib.priorities.Priority. 102 @param timeout: The max lifetime of the suite in hours. 103 @param file_bugs: True if bug filing is desired for this suite. 104 @param firmware_rw_build: Firmware build to update RW firmware. Default 105 to None. 106 @param test_source_build: Build that contains the server-side test code. 107 Default to None to use the ChromeOS build 108 (defined by `build`). 109 @param job_retry: Set to True to enable job-level retry. Default is 110 False. 111 112 @return True if the suite got scheduled 113 @raise ScheduleException if an error occurs while scheduling. 114 115 """ 116 try: 117 builds = {provision.CROS_VERSION_PREFIX: build} 118 if firmware_rw_build: 119 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build 120 logging.info('Scheduling %s on %s against %s (pool: %s)', 121 suite, builds, board, pool) 122 if self._afe.run( 123 'create_suite_job', name=suite, board=board, 124 builds=builds, check_hosts=False, num=num, pool=pool, 125 priority=priority, timeout=timeout, file_bugs=file_bugs, 126 wait_for_results=file_bugs, 127 test_source_build=test_source_build, 128 job_retry=job_retry) is not None: 129 return True 130 else: 131 raise ScheduleException( 132 "Can't schedule %s for %s." % (suite, builds)) 133 except (error.ControlFileNotFound, error.ControlFileEmpty, 134 error.ControlFileMalformed, error.NoControlFileList) as e: 135 if self._file_bug: 136 # File bug on test_source_build if it's specified. 137 b = reporting.SuiteSchedulerBug( 138 suite, test_source_build or build, board, e) 139 # If a bug has filed with the same <suite, build, error type> 140 # will not file again, but simply gets the existing bug id. 141 bid, _ = reporting.Reporter().report( 142 b, ignore_duplicate=True) 143 if bid is not None: 144 return False 145 # Raise the exception if not filing a bug or failed to file bug. 146 raise ScheduleException(e) 147 except Exception as e: 148 raise ScheduleException(e) 149 150 151 def ScheduleSuite(self, suite, board, build, pool, num, priority, timeout, 152 force=False, file_bugs=False, firmware_rw_build=None, 153 test_source_build=None, job_retry=False): 154 """Schedule |suite|, if it hasn't already been run. 155 156 If |suite| has not already been run against |build| on |board|, 157 schedule it and return True. If it has, return False. 158 159 @param suite: the name of the suite to run, e.g. 'bvt' 160 @param board: the board to run the suite on, e.g. x86-alex 161 @param build: the ChromeOS build to install e.g. 162 x86-alex-release/R18-1655.0.0-a1-b1584. 163 @param pool: the pool of machines to use for scheduling purposes. 164 @param num: the number of devices across which to shard the test suite. 165 Type: integer or None 166 @param priority: One of the values from 167 client.common_lib.priorities.Priority. 168 @param timeout: The max lifetime of the suite in hours. 169 @param force: Always schedule the suite. 170 @param file_bugs: True if bug filing is desired for this suite. 171 @param firmware_rw_build: Firmware build to update RW firmware. Default 172 to None. 173 @param test_source_build: Build with the source of tests. Default to 174 None to use the ChromeOS build. 175 @param job_retry: Set to True to enable job-level retry. Default is 176 False. 177 178 @return True if the suite got scheduled, False if not 179 @raise DedupException if we can't check for dups. 180 @raise ScheduleException if the suite cannot be scheduled. 181 182 """ 183 if (force or self._ShouldScheduleSuite(suite, board, 184 test_source_build or build)): 185 return self._Schedule(suite, board, build, pool, num, priority, 186 timeout, file_bugs=file_bugs, 187 firmware_rw_build=firmware_rw_build, 188 test_source_build=test_source_build, 189 job_retry=job_retry) 190 return False 191 192 193 def CheckHostsExist(self, *args, **kwargs): 194 """Forward a request to check if hosts matching args, kwargs exist.""" 195 try: 196 return self._afe.get_hostnames(*args, **kwargs) 197 except error.TimeoutException as e: 198 logging.exception(e) 199 return [] 200