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 6import logging 7import re 8import subprocess 9 10import base_event 11import deduping_scheduler 12import driver 13import manifest_versions 14from distutils import version 15from constants import Labels 16from constants import Builds 17 18import common 19from autotest_lib.server import utils as server_utils 20from autotest_lib.server.cros.dynamic_suite import constants 21 22 23class MalformedConfigEntry(Exception): 24 """Raised to indicate a failure to parse a Task out of a config.""" 25 pass 26 27 28BARE_BRANCHES = ['factory', 'firmware'] 29 30 31def 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 43class 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 122class 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 737class 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