Home | History | Annotate | Download | only in suite_scheduler
      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 
      5 
      6 import logging
      7 import re
      8 import subprocess
      9 
     10 import base_event
     11 import deduping_scheduler
     12 import driver
     13 import manifest_versions
     14 from distutils import version
     15 from constants import Labels
     16 from constants import Builds
     17 
     18 import common
     19 from autotest_lib.server import utils as server_utils
     20 from autotest_lib.server.cros.dynamic_suite import constants
     21 
     22 
     23 class MalformedConfigEntry(Exception):
     24     """Raised to indicate a failure to parse a Task out of a config."""
     25     pass
     26 
     27 
     28 BARE_BRANCHES = ['factory', 'firmware']
     29 
     30 
     31 def PickBranchName(type, milestone):
     32     """Pick branch name. If type is among BARE_BRANCHES, return type,
     33     otherwise, return milestone.
     34 
     35     @param type: type of the branch, e.g., 'release', 'factory', or 'firmware'
     36     @param milestone: CrOS milestone number
     37     """
     38     if type in BARE_BRANCHES:
     39         return type
     40     return milestone
     41 
     42 
     43 class TotMilestoneManager(object):
     44     """A class capable of converting tot string to milestone numbers.
     45 
     46     This class is used as a cache for the tot milestone, so we don't
     47     repeatedly hit google storage for all O(100) tasks in suite
     48     scheduler's ini file.
     49     """
     50 
     51     __metaclass__ = server_utils.Singleton
     52 
     53     # True if suite_scheduler is running for sanity check. When it's set to
     54     # True, the code won't make gsutil call to get the actual tot milestone to
     55     # avoid dependency on the installation of gsutil to run sanity check.
     56     is_sanity = False
     57 
     58 
     59     @staticmethod
     60     def _tot_milestone():
     61         """Get the tot milestone, eg: R40
     62 
     63         @returns: A string representing the Tot milestone as declared by
     64             the LATEST_BUILD_URL, or an empty string if LATEST_BUILD_URL
     65             doesn't exist.
     66         """
     67         if TotMilestoneManager.is_sanity:
     68             logging.info('suite_scheduler is running for sanity purpose, no '
     69                          'need to get the actual tot milestone string.')
     70             return 'R40'
     71 
     72         cmd = ['gsutil', 'cat', constants.LATEST_BUILD_URL]
     73         proc = subprocess.Popen(
     74                 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     75         stdout, stderr = proc.communicate()
     76         if proc.poll():
     77             logging.warning('Failed to get latest build: %s', stderr)
     78             return ''
     79         return stdout.split('-')[0]
     80 
     81 
     82     def refresh(self):
     83         """Refresh the tot milestone string managed by this class."""
     84         self.tot = self._tot_milestone()
     85 
     86 
     87     def __init__(self):
     88         """Initialize a TotMilestoneManager."""
     89         self.refresh()
     90 
     91 
     92     def ConvertTotSpec(self, tot_spec):
     93         """Converts a tot spec to the appropriate milestone.
     94 
     95         Assume tot is R40:
     96         tot   -> R40
     97         tot-1 -> R39
     98         tot-2 -> R38
     99         tot-(any other numbers) -> R40
    100 
    101         With the last option one assumes that a malformed configuration that has
    102         'tot' in it, wants at least tot.
    103 
    104         @param tot_spec: A string representing the tot spec.
    105         @raises MalformedConfigEntry: If the tot_spec doesn't match the
    106             expected format.
    107         """
    108         tot_spec = tot_spec.lower()
    109         match = re.match('(tot)[-]?(1$|2$)?', tot_spec)
    110         if not match:
    111             raise MalformedConfigEntry(
    112                     "%s isn't a valid branch spec." % tot_spec)
    113         tot_mstone = self.tot
    114         num_back = match.groups()[1]
    115         if num_back:
    116             tot_mstone_num = tot_mstone.lstrip('R')
    117             tot_mstone = tot_mstone.replace(
    118                     tot_mstone_num, str(int(tot_mstone_num)-int(num_back)))
    119         return tot_mstone
    120 
    121 
    122 class Task(object):
    123     """Represents an entry from the scheduler config.  Can schedule itself.
    124 
    125     Each entry from the scheduler config file maps one-to-one to a
    126     Task.  Each instance has enough info to schedule itself
    127     on-demand with the AFE.
    128 
    129     This class also overrides __hash__() and all comparator methods to enable
    130     correct use in dicts, sets, etc.
    131     """
    132 
    133 
    134     @staticmethod
    135     def CreateFromConfigSection(config, section):
    136         """Create a Task from a section of a config file.
    137 
    138         The section to parse should look like this:
    139         [TaskName]
    140         suite: suite_to_run  # Required
    141         run_on: event_on which to run  # Required
    142         hour: integer of the hour to run, only applies to nightly. # Optional
    143         branch_specs: factory,firmware,>=R12 or ==R12 # Optional
    144         pool: pool_of_devices  # Optional
    145         num: sharding_factor  # int, Optional
    146         boards: board1, board2  # comma seperated string, Optional
    147 
    148         By default, Tasks run on all release branches, not factory or firmware.
    149 
    150         @param config: a ForgivingConfigParser.
    151         @param section: the section to parse into a Task.
    152         @return keyword, Task object pair.  One or both will be None on error.
    153         @raise MalformedConfigEntry if there's a problem parsing |section|.
    154         """
    155         if not config.has_section(section):
    156             raise MalformedConfigEntry('unknown section %s' % section)
    157 
    158         allowed = set(['suite', 'run_on', 'branch_specs', 'pool', 'num',
    159                        'boards', 'file_bugs', 'cros_build_spec',
    160                        'firmware_rw_build_spec', 'test_source', 'job_retry',
    161                        'hour', 'day'])
    162         # The parameter of union() is the keys under the section in the config
    163         # The union merges this with the allowed set, so if any optional keys
    164         # are omitted, then they're filled in. If any extra keys are present,
    165         # then they will expand unioned set, causing it to fail the following
    166         # comparison against the allowed set.
    167         section_headers = allowed.union(dict(config.items(section)).keys())
    168         if allowed != section_headers:
    169             raise MalformedConfigEntry('unknown entries: %s' %
    170                       ", ".join(map(str, section_headers.difference(allowed))))
    171 
    172         keyword = config.getstring(section, 'run_on')
    173         hour = config.getstring(section, 'hour')
    174         suite = config.getstring(section, 'suite')
    175         branches = config.getstring(section, 'branch_specs')
    176         pool = config.getstring(section, 'pool')
    177         boards = config.getstring(section, 'boards')
    178         file_bugs = config.getboolean(section, 'file_bugs')
    179         cros_build_spec = config.getstring(section, 'cros_build_spec')
    180         firmware_rw_build_spec = config.getstring(
    181                 section, 'firmware_rw_build_spec')
    182         test_source = config.getstring(section, 'test_source')
    183         job_retry = config.getboolean(section, 'job_retry')
    184         for klass in driver.Driver.EVENT_CLASSES:
    185             if klass.KEYWORD == keyword:
    186                 priority = klass.PRIORITY
    187                 timeout = klass.TIMEOUT
    188                 break
    189         else:
    190             priority = None
    191             timeout = None
    192         try:
    193             num = config.getint(section, 'num')
    194         except ValueError as e:
    195             raise MalformedConfigEntry("Ill-specified 'num': %r" % e)
    196         if not keyword:
    197             raise MalformedConfigEntry('No event to |run_on|.')
    198         if not suite:
    199             raise MalformedConfigEntry('No |suite|')
    200         try:
    201             hour = config.getint(section, 'hour')
    202         except ValueError as e:
    203             raise MalformedConfigEntry("Ill-specified 'hour': %r" % e)
    204         if hour is not None and (hour < 0 or hour > 23):
    205             raise MalformedConfigEntry(
    206                     '`hour` must be an integer between 0 and 23.')
    207         if hour is not None and keyword != 'nightly':
    208             raise MalformedConfigEntry(
    209                     '`hour` is the trigger time that can only apply to nightly '
    210                     'event.')
    211 
    212         try:
    213             day = config.getint(section, 'day')
    214         except ValueError as e:
    215             raise MalformedConfigEntry("Ill-specified 'day': %r" % e)
    216         if day is not None and (day < 0 or day > 6):
    217             raise MalformedConfigEntry(
    218                     '`day` must be an integer between 0 and 6, where 0 is for '
    219                     'Monday and 6 is for Sunday.')
    220         if day is not None and keyword != 'weekly':
    221             raise MalformedConfigEntry(
    222                     '`day` is the trigger of the day of a week, that can only '
    223                     'apply to weekly events.')
    224 
    225         specs = []
    226         if branches:
    227             specs = re.split('\s*,\s*', branches)
    228             Task.CheckBranchSpecs(specs)
    229         return keyword, Task(section, suite, specs, pool, num, boards,
    230                              priority, timeout,
    231                              file_bugs=file_bugs if file_bugs else False,
    232                              cros_build_spec=cros_build_spec,
    233                              firmware_rw_build_spec=firmware_rw_build_spec,
    234                              test_source=test_source, job_retry=job_retry,
    235                              hour=hour, day=day)
    236 
    237 
    238     @staticmethod
    239     def CheckBranchSpecs(branch_specs):
    240         """Make sure entries in the list branch_specs are correctly formed.
    241 
    242         We accept any of BARE_BRANCHES in |branch_specs|, as
    243         well as _one_ string of the form '>=RXX' or '==RXX', where 'RXX' is a
    244         CrOS milestone number.
    245 
    246         @param branch_specs: an iterable of branch specifiers.
    247         @raise MalformedConfigEntry if there's a problem parsing |branch_specs|.
    248         """
    249         have_seen_numeric_constraint = False
    250         for branch in branch_specs:
    251             if branch in BARE_BRANCHES:
    252                 continue
    253             if not have_seen_numeric_constraint:
    254                 #TODO(beeps): Why was <= dropped on the floor?
    255                 if branch.startswith('>=R') or branch.startswith('==R'):
    256                     have_seen_numeric_constraint = True
    257                 elif 'tot' in branch:
    258                     TotMilestoneManager().ConvertTotSpec(
    259                             branch[branch.index('tot'):])
    260                     have_seen_numeric_constraint = True
    261                 continue
    262             raise MalformedConfigEntry("%s isn't a valid branch spec." % branch)
    263 
    264 
    265     def __init__(self, name, suite, branch_specs, pool=None, num=None,
    266                  boards=None, priority=None, timeout=None, file_bugs=False,
    267                  cros_build_spec=None, firmware_rw_build_spec=None,
    268                  test_source=None, job_retry=False, hour=None, day=None):
    269         """Constructor
    270 
    271         Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
    272         we'll store them such that _FitsSpec() can be used to check whether a
    273         given branch 'fits' with the specifications passed in here.
    274         For example, given branch_specs = ['factory', '>=R18'], we'd set things
    275         up so that _FitsSpec() would return True for 'factory', or 'RXX'
    276         where XX is a number >= 18. Same check is done for branch_specs = [
    277         'factory', '==R18'], which limit the test to only one specific branch.
    278 
    279         Given branch_specs = ['factory', 'firmware'], _FitsSpec()
    280         would pass only those two specific strings.
    281 
    282         Example usage:
    283           t = Task('Name', 'suite', ['factory', '>=R18'])
    284           t._FitsSpec('factory')  # True
    285           t._FitsSpec('R19')  # True
    286           t._FitsSpec('R17')  # False
    287           t._FitsSpec('firmware')  # False
    288           t._FitsSpec('goober')  # False
    289 
    290           t = Task('Name', 'suite', ['factory', '==R18'])
    291           t._FitsSpec('R19')  # False, branch does not equal to 18
    292           t._FitsSpec('R18')  # True
    293           t._FitsSpec('R17')  # False
    294 
    295         cros_build_spec and firmware_rw_build_spec are set for tests require
    296         firmware update on the dut. Only one of them can be set.
    297         For example:
    298         branch_specs: ==tot
    299         firmware_rw_build_spec: firmware
    300         test_source: cros
    301         This will run test using latest build on firmware branch, and the latest
    302         ChromeOS build on ToT. The test source build is ChromeOS build.
    303 
    304         branch_specs: firmware
    305         cros_build_spec: ==tot-1
    306         test_source: firmware_rw
    307         This will run test using latest build on firmware branch, and the latest
    308         ChromeOS build on dev channel (ToT-1). The test source build is the
    309         firmware RW build.
    310 
    311         branch_specs: ==tot
    312         firmware_rw_build_spec: cros
    313         test_source: cros
    314         This will run test using latest ChromeOS and firmware RW build on ToT.
    315         ChromeOS build on ToT. The test source build is ChromeOS build.
    316 
    317         @param name: name of this task, e.g. 'NightlyPower'
    318         @param suite: the name of the suite to run, e.g. 'bvt'
    319         @param branch_specs: a pre-vetted iterable of branch specifiers,
    320                              e.g. ['>=R18', 'factory']
    321         @param pool: the pool of machines to use for scheduling purposes.
    322                      Default: None
    323         @param num: the number of devices across which to shard the test suite.
    324                     Type: integer or None
    325                     Default: None
    326         @param boards: A comma separated list of boards to run this task on.
    327                        Default: Run on all boards.
    328         @param priority: The string name of a priority from
    329                          client.common_lib.priorities.Priority.
    330         @param timeout: The max lifetime of the suite in hours.
    331         @param file_bugs: True if bug filing is desired for the suite created
    332                           for this task.
    333         @param cros_build_spec: Spec used to determine the ChromeOS build to
    334                                 test with a firmware build, e.g., tot, R41 etc.
    335         @param firmware_rw_build_spec: Spec used to determine the firmware build
    336                                        test with a ChromeOS build.
    337         @param test_source: The source of test code when firmware will be
    338                             updated in the test. The value can be `firmware_rw`
    339                             or `cros`.
    340         @param job_retry: Set to True to enable job-level retry. Default is
    341                           False.
    342         @param hour: An integer specifying the hour that a nightly run should
    343                      be triggered, default is set to 21.
    344         @param day: An integer specifying the day of a week that a weekly run
    345                     should be triggered, default is set to 5, which is Saturday.
    346         """
    347         self._name = name
    348         self._suite = suite
    349         self._branch_specs = branch_specs
    350         self._pool = pool
    351         self._num = num
    352         self._priority = priority
    353         self._timeout = timeout
    354         self._file_bugs = file_bugs
    355         self._cros_build_spec = cros_build_spec
    356         self._firmware_rw_build_spec = firmware_rw_build_spec
    357         self._test_source = test_source
    358         self._job_retry = job_retry
    359         self.hour = hour
    360         self.day = day
    361 
    362         if ((self._firmware_rw_build_spec or cros_build_spec) and
    363             not self.test_source in [Builds.FIRMWARE_RW, Builds.CROS]):
    364             raise MalformedConfigEntry(
    365                     'You must specify the build for test source. It can only '
    366                     'be `firmware_rw` or `cros`.')
    367         if self._firmware_rw_build_spec and cros_build_spec:
    368             raise MalformedConfigEntry(
    369                     'You cannot specify both firmware_rw_build_spec and '
    370                     'cros_build_spec. firmware_rw_build_spec is used to specify'
    371                     ' a firmware build when the suite requires firmware to be '
    372                     'updated in the dut, its value can only be `firmware` or '
    373                     '`cros`. cros_build_spec is used to specify a ChromeOS '
    374                     'build when build_specs is set to firmware.')
    375         if (self._firmware_rw_build_spec and
    376             self._firmware_rw_build_spec not in ['firmware', 'cros']):
    377             raise MalformedConfigEntry(
    378                     'firmware_rw_build_spec can only be empty, firmware or '
    379                     'cros. It does not support other build type yet.')
    380 
    381         self._bare_branches = []
    382         self._version_equal_constraint = False
    383         self._version_gte_constraint = False
    384         self._version_lte_constraint = False
    385         if not branch_specs:
    386             # Any milestone is OK.
    387             self._numeric_constraint = version.LooseVersion('0')
    388         else:
    389             self._numeric_constraint = None
    390             for spec in branch_specs:
    391                 if 'tot' in spec.lower():
    392                     tot_str = spec[spec.index('tot'):]
    393                     spec = spec.replace(
    394                             tot_str, TotMilestoneManager().ConvertTotSpec(
    395                                     tot_str))
    396                 if spec.startswith('>='):
    397                     self._numeric_constraint = version.LooseVersion(
    398                             spec.lstrip('>=R'))
    399                     self._version_gte_constraint = True
    400                 elif spec.startswith('<='):
    401                     self._numeric_constraint = version.LooseVersion(
    402                             spec.lstrip('<=R'))
    403                     self._version_lte_constraint = True
    404                 elif spec.startswith('=='):
    405                     self._version_equal_constraint = True
    406                     self._numeric_constraint = version.LooseVersion(
    407                             spec.lstrip('==R'))
    408                 else:
    409                     self._bare_branches.append(spec)
    410 
    411         # Since we expect __hash__() and other comparator methods to be used
    412         # frequently by set operations, and they use str() a lot, pre-compute
    413         # the string representation of this object.
    414         if num is None:
    415             numStr = '[Default num]'
    416         else:
    417             numStr = '%d' % num
    418 
    419         if boards is None:
    420             self._boards = set()
    421             boardsStr = '[All boards]'
    422         else:
    423             self._boards = set([x.strip() for x in boards.split(',')])
    424             boardsStr = boards
    425 
    426         self._str = ('%s: %s on %s with pool %s, boards [%s], file_bugs = %s '
    427                      'across %s machines.' % (self.__class__.__name__,
    428                      suite, branch_specs, pool, boardsStr, self._file_bugs,
    429                      numStr))
    430 
    431 
    432     def _FitsSpec(self, branch):
    433         """Checks if a branch is deemed OK by this instance's branch specs.
    434 
    435         When called on a branch name, will return whether that branch
    436         'fits' the specifications stored in self._bare_branches,
    437         self._numeric_constraint, self._version_equal_constraint,
    438         self._version_gte_constraint and self._version_lte_constraint.
    439 
    440         @param branch: the branch to check.
    441         @return True if b 'fits' with stored specs, False otherwise.
    442         """
    443         if branch in BARE_BRANCHES:
    444             return branch in self._bare_branches
    445         if self._numeric_constraint:
    446             if self._version_equal_constraint:
    447                 return version.LooseVersion(branch) == self._numeric_constraint
    448             elif self._version_gte_constraint:
    449                 return version.LooseVersion(branch) >= self._numeric_constraint
    450             elif self._version_lte_constraint:
    451                 return version.LooseVersion(branch) <= self._numeric_constraint
    452             else:
    453                 # Default to great or equal constraint.
    454                 return version.LooseVersion(branch) >= self._numeric_constraint
    455         else:
    456             return False
    457 
    458 
    459     @property
    460     def name(self):
    461         """Name of this task, e.g. 'NightlyPower'."""
    462         return self._name
    463 
    464 
    465     @property
    466     def suite(self):
    467         """Name of the suite to run, e.g. 'bvt'."""
    468         return self._suite
    469 
    470 
    471     @property
    472     def branch_specs(self):
    473         """a pre-vetted iterable of branch specifiers,
    474         e.g. ['>=R18', 'factory']."""
    475         return self._branch_specs
    476 
    477 
    478     @property
    479     def pool(self):
    480         """The pool of machines to use for scheduling purposes."""
    481         return self._pool
    482 
    483 
    484     @property
    485     def num(self):
    486         """The number of devices across which to shard the test suite.
    487         Type: integer or None"""
    488         return self._num
    489 
    490 
    491     @property
    492     def boards(self):
    493         """The boards on which to run this suite.
    494         Type: Iterable of strings"""
    495         return self._boards
    496 
    497 
    498     @property
    499     def priority(self):
    500         """The priority of the suite"""
    501         return self._priority
    502 
    503 
    504     @property
    505     def timeout(self):
    506         """The maximum lifetime of the suite in hours."""
    507         return self._timeout
    508 
    509 
    510     @property
    511     def cros_build_spec(self):
    512         """The build spec of ChromeOS to test with a firmware build."""
    513         return self._cros_build_spec
    514 
    515 
    516     @property
    517     def firmware_rw_build_spec(self):
    518         """The build spec of firmware to test with a ChromeOS build."""
    519         return self._firmware_rw_build_spec
    520 
    521 
    522     @property
    523     def test_source(self):
    524         """Source of the test code, value can be `firmware_rw` or `cros`."""
    525         return self._test_source
    526 
    527 
    528     def __str__(self):
    529         return self._str
    530 
    531 
    532     def __repr__(self):
    533         return self._str
    534 
    535 
    536     def __lt__(self, other):
    537         return str(self) < str(other)
    538 
    539 
    540     def __le__(self, other):
    541         return str(self) <= str(other)
    542 
    543 
    544     def __eq__(self, other):
    545         return str(self) == str(other)
    546 
    547 
    548     def __ne__(self, other):
    549         return str(self) != str(other)
    550 
    551 
    552     def __gt__(self, other):
    553         return str(self) > str(other)
    554 
    555 
    556     def __ge__(self, other):
    557         return str(self) >= str(other)
    558 
    559 
    560     def __hash__(self):
    561         """Allows instances to be correctly deduped when used in a set."""
    562         return hash(str(self))
    563 
    564 
    565     def _GetCrOSBuild(self, mv, board):
    566         """Get the ChromeOS build name to test with firmware build.
    567 
    568         The ChromeOS build to be used is determined by `self.cros_build_spec`.
    569         Its value can be:
    570         tot: use the latest ToT build.
    571         tot-x: use the latest build in x milestone before ToT.
    572         Rxx: use the latest build on xx milestone.
    573 
    574         @param board: the board against which to run self._suite.
    575         @param mv: an instance of manifest_versions.ManifestVersions.
    576 
    577         @return: The ChromeOS build name to test with firmware build.
    578 
    579         """
    580         if not self.cros_build_spec:
    581             return None
    582         if self.cros_build_spec.startswith('tot'):
    583             milestone = TotMilestoneManager().ConvertTotSpec(
    584                     self.cros_build_spec)[1:]
    585         elif self.cros_build_spec.startswith('R'):
    586             milestone = self.cros_build_spec[1:]
    587         milestone, latest_manifest = mv.GetLatestManifest(
    588                 board, 'release', milestone=milestone)
    589         latest_build = base_event.BuildName(board, 'release', milestone,
    590                                             latest_manifest)
    591         logging.debug('Found latest build of %s for spec %s: %s',
    592                       board, self.cros_build_spec, latest_build)
    593         return latest_build
    594 
    595 
    596     def _GetFirmwareRWBuild(self, mv, board, build_type):
    597         """Get the firmware rw build name to test with ChromeOS build.
    598 
    599         The firmware rw build to be used is determined by
    600         `self.firmware_rw_build_spec`. Its value can be `firmware`, `cros` or
    601         empty:
    602         firmware: use the ToT build in firmware branch.
    603         cros: use the ToT build in release (ChromeOS) branch.
    604 
    605         @param mv: an instance of manifest_versions.ManifestVersions.
    606         @param board: the board against which to run self._suite.
    607         @param build_type: Build type of the firmware build, e.g., factory,
    608                            firmware or release.
    609 
    610         @return: The firmware rw build name to test with ChromeOS build.
    611 
    612         """
    613         if not self.firmware_rw_build_spec:
    614             return None
    615         latest_milestone, latest_manifest = mv.GetLatestManifest(
    616                 board, build_type)
    617         latest_build = base_event.BuildName(board, build_type, latest_milestone,
    618                                             latest_manifest)
    619         logging.debug('Found latest firmware build of %s for spec %s: %s',
    620                       board, self.firmware_rw_build_spec, latest_build)
    621         return latest_build
    622 
    623 
    624     def AvailableHosts(self, scheduler, board):
    625         """Query what hosts are able to run a test on a board and pool
    626         combination.
    627 
    628         @param scheduler: an instance of DedupingScheduler, as defined in
    629                           deduping_scheduler.py
    630         @param board: the board against which one wants to run the test.
    631         @return The list of hosts meeting the board and pool requirements,
    632                 or None if no hosts were found."""
    633         if self._boards and board not in self._boards:
    634             return []
    635 
    636         labels = [Labels.BOARD_PREFIX + board]
    637         if self._pool:
    638             labels.append(Labels.POOL_PREFIX + self._pool)
    639 
    640         return scheduler.CheckHostsExist(multiple_labels=labels)
    641 
    642 
    643     def ShouldHaveAvailableHosts(self):
    644         """As a sanity check, return true if we know for certain that
    645         we should be able to schedule this test. If we claim this test
    646         should be able to run, and it ends up not being scheduled, then
    647         a warning will be reported.
    648 
    649         @return True if this test should be able to run, False otherwise.
    650         """
    651         return self._pool == 'bvt'
    652 
    653 
    654     def Run(self, scheduler, branch_builds, board, force=False, mv=None):
    655         """Run this task.  Returns False if it should be destroyed.
    656 
    657         Execute this task.  Attempt to schedule the associated suite.
    658         Return True if this task should be kept around, False if it
    659         should be destroyed.  This allows for one-shot Tasks.
    660 
    661         @param scheduler: an instance of DedupingScheduler, as defined in
    662                           deduping_scheduler.py
    663         @param branch_builds: a dict mapping branch name to the build(s) to
    664                               install for that branch, e.g.
    665                               {'R18': ['x86-alex-release/R18-1655.0.0'],
    666                                'R19': ['x86-alex-release/R19-2077.0.0']}
    667         @param board: the board against which to run self._suite.
    668         @param force: Always schedule the suite.
    669         @param mv: an instance of manifest_versions.ManifestVersions.
    670 
    671         @return True if the task should be kept, False if not
    672 
    673         """
    674         logging.info('Running %s on %s', self._name, board)
    675         is_firmware_build = 'firmware' in self.branch_specs
    676         # firmware_rw_build is only needed if firmware_rw_build_spec is given.
    677         firmware_rw_build = None
    678         try:
    679             if is_firmware_build:
    680                 # When build specified in branch_specs is a firmware build,
    681                 # we need a ChromeOS build to test with the firmware build.
    682                 cros_build = self._GetCrOSBuild(mv, board)
    683             elif self.firmware_rw_build_spec:
    684                 # When firmware_rw_build_spec is specified, the test involves
    685                 # updating the firmware by firmware build specified in
    686                 # firmware_rw_build_spec.
    687                 if self.firmware_rw_build_spec == 'cros':
    688                     build_type = 'release'
    689                 else:
    690                     build_type = self.firmware_rw_build_spec
    691                 firmware_rw_build = self._GetFirmwareRWBuild(
    692                         mv, board, build_type)
    693         except manifest_versions.QueryException as e:
    694             logging.error(e)
    695             logging.error('Running %s on %s is failed. Failed to find build '
    696                           'required to run the suite.', self._name, board)
    697             return False
    698 
    699         builds = []
    700         for branch, build in branch_builds.iteritems():
    701             logging.info('Checking if %s fits spec %r',
    702                          branch, self.branch_specs)
    703             if self._FitsSpec(branch):
    704                 logging.debug('Build %s fits the spec.', build)
    705                 builds.extend(build)
    706         for build in builds:
    707             try:
    708                 if is_firmware_build:
    709                     firmware_rw_build = build
    710                 else:
    711                     cros_build = build
    712                 if self.test_source == Builds.FIRMWARE_RW:
    713                     test_source_build = firmware_rw_build
    714                 elif self.test_source == Builds.CROS:
    715                     test_source_build = cros_build
    716                 else:
    717                     test_source_build = None
    718                 logging.debug('Schedule %s for builds %s.%s',
    719                               self._suite, builds,
    720                               (' Test source build is %s.' % test_source_build)
    721                               if test_source_build else None)
    722 
    723                 if not scheduler.ScheduleSuite(
    724                         self._suite, board, cros_build, self._pool, self._num,
    725                         self._priority, self._timeout, force,
    726                         file_bugs=self._file_bugs,
    727                         firmware_rw_build=firmware_rw_build,
    728                         test_source_build=test_source_build,
    729                         job_retry=self._job_retry):
    730                     logging.info('Skipping scheduling %s on %s for %s',
    731                                  self._suite, builds, board)
    732             except deduping_scheduler.DedupingSchedulerException as e:
    733                 logging.error(e)
    734         return True
    735 
    736 
    737 class OneShotTask(Task):
    738     """A Task that can be run only once.  Can schedule itself."""
    739 
    740 
    741     def Run(self, scheduler, branch_builds, board, force=False, mv=None):
    742         """Run this task.  Returns False, indicating it should be destroyed.
    743 
    744         Run this task.  Attempt to schedule the associated suite.
    745         Return False, indicating to the caller that it should discard this task.
    746 
    747         @param scheduler: an instance of DedupingScheduler, as defined in
    748                           deduping_scheduler.py
    749         @param branch_builds: a dict mapping branch name to the build(s) to
    750                               install for that branch, e.g.
    751                               {'R18': ['x86-alex-release/R18-1655.0.0'],
    752                                'R19': ['x86-alex-release/R19-2077.0.0']}
    753         @param board: the board against which to run self._suite.
    754         @param force: Always schedule the suite.
    755         @param mv: an instance of manifest_versions.ManifestVersions.
    756 
    757         @return False
    758 
    759         """
    760         super(OneShotTask, self).Run(scheduler, branch_builds, board, force,
    761                                      mv)
    762         return False
    763