• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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