def CheckBranchSpecs(branch_specs): """Make sure entries in the list branch_specs are correctly formed. We accept any of BARE_BRANCHES in |branch_specs|, as well as _one_ string of the form '>=RXX' or '==RXX', where 'RXX' is a CrOS milestone number. @param branch_specs: an iterable of branch specifiers. @raise MalformedConfigEntry if there's a problem parsing |branch_specs|. """ have_seen_numeric_constraint = False for branch in branch_specs: if branch in BARE_BRANCHES: continue if not have_seen_numeric_constraint: #TODO(beeps): Why was <= dropped on the floor? if branch.startswith('>=R') or branch.startswith('==R'): have_seen_numeric_constraint = True elif 'tot' in branch: TotMilestoneManager().ConvertTotSpec( branch[branch.index('tot'):]) have_seen_numeric_constraint = True continue raise error.MalformedConfigEntry("%s isn't a valid branch spec.'" % branch)
def ConvertTotSpec(self, tot_spec): """Converts a tot spec to the appropriate milestone. Assume tot is R40: tot -> R40 tot-1 -> R39 tot-2 -> R38 tot-(any other numbers) -> R40 With the last option one assumes that a malformed configuration that has 'tot' in it, wants at least tot. @param tot_spec: A string representing the tot spec. @raises MalformedConfigEntry: If the tot_spec doesn't match the expected format. """ tot_spec = tot_spec.lower() match = re.match('(tot)[-]?(1$|2$)?', tot_spec) if not match: raise error.MalformedConfigEntry("%s isn't a valid branch spec." % tot_spec) tot_mstone = self.tot num_back = match.groups()[1] if num_back: tot_mstone_num = tot_mstone.lstrip('R') tot_mstone = tot_mstone.replace( tot_mstone_num, str(int(tot_mstone_num) - int(num_back))) return tot_mstone
def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except ConfigParser.Error: return None except ValueError as e: raise error.MalformedConfigEntry(str(e))
def _ReadBoardWhitelist(self, config): """Read board whitelist from config and save as dict. @param config: an instance of ForgivingConfigParser. """ board_lists = {} if BOARD_WHITELIST_SECTION not in config.sections(): return board_lists for option in config.options(BOARD_WHITELIST_SECTION): if option in board_lists: raise error.MalformedConfigEntry( 'Board list name must be unique.') else: board_lists[option] = config.getstring( BOARD_WHITELIST_SECTION, option) return board_lists
def __init__(self, name, suite, branch_specs, pool=None, num=None, boards=None, priority=None, timeout=None, file_bugs=False, cros_build_spec=None, firmware_rw_build_spec=None, firmware_ro_build_spec=None, test_source=None, job_retry=False, hour=None, day=None, os_type=OS_TYPE_CROS, launch_control_branches=None, launch_control_targets=None, testbed_dut_count=None, no_delay=False): """Constructor Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs, we'll store them such that _FitsSpec() can be used to check whether a given branch 'fits' with the specifications passed in here. For example, given branch_specs = ['factory', '>=R18'], we'd set things up so that _FitsSpec() would return True for 'factory', or 'RXX' where XX is a number >= 18. Same check is done for branch_specs = [ 'factory', '==R18'], which limit the test to only one specific branch. Given branch_specs = ['factory', 'firmware'], _FitsSpec() would pass only those two specific strings. Example usage: t = Task('Name', 'suite', ['factory', '>=R18']) t._FitsSpec('factory') # True t._FitsSpec('R19') # True t._FitsSpec('R17') # False t._FitsSpec('firmware') # False t._FitsSpec('goober') # False t = Task('Name', 'suite', ['factory', '==R18']) t._FitsSpec('R19') # False, branch does not equal to 18 t._FitsSpec('R18') # True t._FitsSpec('R17') # False cros_build_spec and firmware_rw_build_spec are set for tests require firmware update on the dut. Only one of them can be set. For example: branch_specs: ==tot firmware_rw_build_spec: firmware test_source: cros This will run test using latest build on firmware branch, and the latest ChromeOS build on ToT. The test source build is ChromeOS build. branch_specs: firmware cros_build_spec: ==tot-1 test_source: firmware_rw This will run test using latest build on firmware branch, and the latest ChromeOS build on dev channel (ToT-1). The test source build is the firmware RW build. branch_specs: ==tot firmware_rw_build_spec: cros test_source: cros This will run test using latest ChromeOS and firmware RW build on ToT. ChromeOS build on ToT. The test source build is ChromeOS build. @param name: name of this task, e.g. 'NightlyPower' @param suite: the name of the suite to run, e.g. 'bvt' @param branch_specs: a pre-vetted iterable of branch specifiers, e.g. ['>=R18', 'factory'] @param pool: the pool of machines to use for scheduling purposes. Default: None @param num: the number of devices across which to shard the test suite. Type: integer or None Default: None @param boards: A comma separated list of boards to run this task on. Default: Run on all boards. @param priority: The string name of a priority from client.common_lib.priorities.Priority. @param timeout: The max lifetime of the suite in hours. @param file_bugs: True if bug filing is desired for the suite created for this task. @param cros_build_spec: Spec used to determine the ChromeOS build to test with a firmware build, e.g., tot, R41 etc. @param firmware_rw_build_spec: Spec used to determine the firmware RW build test with a ChromeOS build. @param firmware_ro_build_spec: Spec used to determine the firmware RO build test with a ChromeOS build. @param test_source: The source of test code when firmware will be updated in the test. The value can be `firmware_rw`, `firmware_ro` or `cros`. @param job_retry: Set to True to enable job-level retry. Default is False. @param hour: An integer specifying the hour that a nightly run should be triggered, default is set to 21. @param day: An integer specifying the day of a week that a weekly run should be triggered, default is set to 5, which is Saturday. @param os_type: Type of OS, e.g., cros, brillo, android. Default is cros. The argument is required for android/brillo builds. @param launch_control_branches: Comma separated string of Launch Control branches. The argument is required and only applicable for android/brillo builds. @param launch_control_targets: Comma separated string of build targets for Launch Control builds. The argument is required and only applicable for android/brillo builds. @param testbed_dut_count: Number of duts to test when using a testbed. @param no_delay: Set to True to allow suite to be created without configuring delay_minutes. Default is False. """ self._name = name self._suite = suite self._branch_specs = branch_specs self._pool = pool self._num = num self._priority = priority self._timeout = timeout self._file_bugs = file_bugs self._cros_build_spec = cros_build_spec self._firmware_rw_build_spec = firmware_rw_build_spec self._firmware_ro_build_spec = firmware_ro_build_spec self._test_source = test_source self._job_retry = job_retry self._hour = hour self._day = day self._os_type = os_type self._launch_control_branches = ([ b.strip() for b in launch_control_branches.split(',') ] if launch_control_branches else []) self._launch_control_targets = ([ t.strip() for t in launch_control_targets.split(',') ] if launch_control_targets else []) self._testbed_dut_count = testbed_dut_count self._no_delay = no_delay if ((self._firmware_rw_build_spec or self._firmware_ro_build_spec or cros_build_spec) and not self.test_source in [Builds.FIRMWARE_RW, Builds.FIRMWARE_RO, Builds.CROS]): raise error.MalformedConfigEntry( 'You must specify the build for test source. It can only ' 'be `firmware_rw`, `firmware_ro` or `cros`.') if self._firmware_rw_build_spec and cros_build_spec: raise error.MalformedConfigEntry( 'You cannot specify both firmware_rw_build_spec and ' 'cros_build_spec. firmware_rw_build_spec is used to specify' ' a firmware build when the suite requires firmware to be ' 'updated in the dut, its value can only be `firmware` or ' '`cros`. cros_build_spec is used to specify a ChromeOS ' 'build when build_specs is set to firmware.') if (self._firmware_rw_build_spec and self._firmware_rw_build_spec not in ['firmware', 'cros']): raise error.MalformedConfigEntry( 'firmware_rw_build_spec can only be empty, firmware or ' 'cros. It does not support other build type yet.') if os_type not in OS_TYPES_LAUNCH_CONTROL and self._testbed_dut_count: raise error.MalformedConfigEntry( 'testbed_dut_count is only applicable to testbed to run ' 'test with builds from Launch Control.') self._bare_branches = [] self._version_equal_constraint = False self._version_gte_constraint = False self._version_lte_constraint = False if not branch_specs: # Any milestone is OK. self._numeric_constraint = version.LooseVersion('0') else: self._numeric_constraint = None for spec in branch_specs: if 'tot' in spec.lower(): tot_str = spec[spec.index('tot'):] spec = spec.replace( tot_str, TotMilestoneManager().ConvertTotSpec(tot_str)) if spec.startswith('>='): self._numeric_constraint = version.LooseVersion( spec.lstrip('>=R')) self._version_gte_constraint = True elif spec.startswith('<='): self._numeric_constraint = version.LooseVersion( spec.lstrip('<=R')) self._version_lte_constraint = True elif spec.startswith('=='): self._version_equal_constraint = True self._numeric_constraint = version.LooseVersion( spec.lstrip('==R')) else: self._bare_branches.append(spec) # Since we expect __hash__() and other comparator methods to be used # frequently by set operations, and they use str() a lot, pre-compute # the string representation of this object. if num is None: numStr = '[Default num]' else: numStr = '%d' % num if boards is None: self._boards = set() boardsStr = '[All boards]' else: self._boards = set([x.strip() for x in boards.split(',')]) boardsStr = boards time_str = '' if self._hour: time_str = ' Run at %d:00.' % self._hour elif self._day: time_str = ' Run on %s.' % _WEEKDAYS[self._day] if os_type == OS_TYPE_CROS: self._str = ('%s: %s on %s with pool %s, boards [%s], file_bugs = ' '%s across %s machines.%s' % (self.__class__.__name__, suite, branch_specs, pool, boardsStr, self._file_bugs, numStr, time_str)) else: testbed_dut_count_str = '.' if self._testbed_dut_count: testbed_dut_count_str = (', each with %d duts.' % self._testbed_dut_count) self._str = ( '%s: %s on branches %s and targets %s with pool %s, ' 'boards [%s], file_bugs = %s across %s machines%s%s' % (self.__class__.__name__, suite, launch_control_branches, launch_control_targets, pool, boardsStr, self._file_bugs, numStr, testbed_dut_count_str, time_str))
def CreateFromConfigSection(config, section, board_lists={}): """Create a Task from a section of a config file. The section to parse should look like this: [TaskName] suite: suite_to_run # Required run_on: event_on which to run # Required hour: integer of the hour to run, only applies to nightly. # Optional branch_specs: factory,firmware,>=R12 or ==R12 # Optional pool: pool_of_devices # Optional num: sharding_factor # int, Optional boards: board1, board2 # comma seperated string, Optional # Settings for Launch Control builds only: os_type: brillo # Type of OS, e.g., cros, brillo, android. Default is cros. Required for android/brillo builds. branches: git_mnc_release # comma separated string of Launch Control branches. Required and only applicable for android/brillo builds. targets: dragonboard-eng # comma separated string of build targets. Required and only applicable for android/brillo builds. testbed_dut_count: Number of duts to test when using a testbed. By default, Tasks run on all release branches, not factory or firmware. @param config: a ForgivingConfigParser. @param section: the section to parse into a Task. @param board_lists: a dict including all board whitelist for tasks. @return keyword, Task object pair. One or both will be None on error. @raise MalformedConfigEntry if there's a problem parsing |section|. """ if not config.has_section(section): raise error.MalformedConfigEntry('unknown section %s' % section) allowed = set([ 'suite', 'run_on', 'branch_specs', 'pool', 'num', 'boards', 'file_bugs', 'cros_build_spec', 'firmware_rw_build_spec', 'firmware_ro_build_spec', 'test_source', 'job_retry', 'hour', 'day', 'branches', 'targets', 'os_type', 'no_delay', 'owner', 'priority', 'timeout' ]) # The parameter of union() is the keys under the section in the config # The union merges this with the allowed set, so if any optional keys # are omitted, then they're filled in. If any extra keys are present, # then they will expand unioned set, causing it to fail the following # comparison against the allowed set. section_headers = allowed.union(dict(config.items(section)).keys()) if allowed != section_headers: raise error.MalformedConfigEntry( 'unknown entries: %s' % ", ".join(map(str, section_headers.difference(allowed)))) keyword = config.getstring(section, 'run_on') hour = config.getstring(section, 'hour') suite = config.getstring(section, 'suite') branch_specs = config.getstring(section, 'branch_specs') pool = config.getstring(section, 'pool') boards = config.getstring(section, 'boards') file_bugs = config.getboolean(section, 'file_bugs') cros_build_spec = config.getstring(section, 'cros_build_spec') firmware_rw_build_spec = config.getstring(section, 'firmware_rw_build_spec') firmware_ro_build_spec = config.getstring(section, 'firmware_ro_build_spec') test_source = config.getstring(section, 'test_source') job_retry = config.getboolean(section, 'job_retry') no_delay = config.getboolean(section, 'no_delay') # In case strings empty use sane low priority defaults. priority = 0 timeout = 24 # Set priority/timeout based on the event type. for klass in driver.Driver.EVENT_CLASSES: if klass.KEYWORD == keyword: priority = klass.PRIORITY timeout = klass.TIMEOUT break # Set priority/timeout from config file explicitly if set. priority_string = config.getstring(section, 'priority') if priority_string: # Try to parse priority as int first. If failed, then use the # global string->priority mapping to lookup its value. try: try: priority = int(priority_string) except ValueError: priority = priorities.Priority.get_value(priority_string) except ValueError: raise error.MalformedConfigEntry( "Priority string not " "recognized as value (%s).", priority_string) timeout_value = config.getint(section, 'timeout') if timeout_value: timeout = timeout_value # Sanity Check for priority and timeout. if priority < 0 or priority > 100: raise error.MalformedConfigEntry('Priority(%d) should be inside ' 'the range 0-100.' % priority) if timeout <= 0: raise error.MalformedConfigEntry( 'Timeout(%d) needs to be positive ' 'integer (hours).' % timeout) try: num = config.getint(section, 'num') except ValueError as e: raise error.MalformedConfigEntry("Ill-specified 'num': %r" % e) if not keyword: raise error.MalformedConfigEntry('No event to |run_on|.') if not suite: raise error.MalformedConfigEntry('No |suite|') try: hour = config.getint(section, 'hour') except ValueError as e: raise error.MalformedConfigEntry("Ill-specified 'hour': %r" % e) if hour is not None and (hour < 0 or hour > 23): raise error.MalformedConfigEntry( '`hour` must be an integer between 0 and 23.') if hour is not None and keyword != 'nightly': raise error.MalformedConfigEntry( '`hour` is the trigger time that can only apply to nightly ' 'event.') testbed_dut_count = None if boards: match = re.match(TESTBED_DUT_COUNT_REGEX, boards) if match: testbed_dut_count = int(match.group(1)) try: day = config.getint(section, 'day') except ValueError as e: raise error.MalformedConfigEntry("Ill-specified 'day': %r" % e) if day is not None and (day < 0 or day > 6): raise error.MalformedConfigEntry( '`day` must be an integer between 0 and 6, where 0 is for ' 'Monday and 6 is for Sunday.') if day is not None and keyword != 'weekly': raise error.MalformedConfigEntry( '`day` is the trigger of the day of a week, that can only ' 'apply to weekly events.') specs = [] if branch_specs: specs = re.split('\s*,\s*', branch_specs) Task.CheckBranchSpecs(specs) os_type = config.getstring(section, 'os_type') or OS_TYPE_CROS if os_type not in OS_TYPES: raise error.MalformedConfigEntry('`os_type` must be one of %s' % OS_TYPES) lc_branches = config.getstring(section, 'branches') lc_targets = config.getstring(section, 'targets') if os_type == OS_TYPE_CROS and (lc_branches or lc_targets): raise error.MalformedConfigEntry( '`branches` and `targets` are only supported for Launch ' 'Control builds, not ChromeOS builds.') if (os_type in OS_TYPES_LAUNCH_CONTROL and (not lc_branches or not lc_targets)): raise error.MalformedConfigEntry( '`branches` and `targets` must be specified for Launch ' 'Control builds.') if (os_type in OS_TYPES_LAUNCH_CONTROL and boards and not testbed_dut_count): raise error.MalformedConfigEntry( '`boards` for Launch Control builds are retrieved from ' '`targets` setting, it should not be set for Launch ' 'Control builds.') if os_type == OS_TYPE_CROS and testbed_dut_count: raise error.MalformedConfigEntry( 'testbed_dut_count is only supported for Launch Control ' 'builds testing with testbed.') # Extract boards from targets list. if os_type in OS_TYPES_LAUNCH_CONTROL: boards = '' for target in lc_targets.split(','): board_name, _ = server_utils.parse_launch_control_target( target.strip()) # Translate board name in build target to the actual board name. board_name = server_utils.ANDROID_TARGET_TO_BOARD_MAP.get( board_name, board_name) boards += '%s,' % board_name boards = boards.strip(',') elif os_type == OS_TYPE_CROS: if board_lists: if boards not in board_lists: logging.debug( 'The board_list name %s does not exist in ' 'section board_lists in config.', boards) # TODO(xixuan): Raise MalformedConfigEntry when a CrOS task # specify a 'boards' which is not defined in board_lists. # Currently exception won't be raised to make sure suite # scheduler keeps running when developers are in the middle # of migrating boards. else: boards = board_lists[boards] return keyword, Task(section, suite, specs, pool, num, boards, priority, timeout, file_bugs=file_bugs if file_bugs else False, cros_build_spec=cros_build_spec, firmware_rw_build_spec=firmware_rw_build_spec, firmware_ro_build_spec=firmware_ro_build_spec, test_source=test_source, job_retry=job_retry, hour=hour, day=day, os_type=os_type, launch_control_branches=lc_branches, launch_control_targets=lc_targets, testbed_dut_count=testbed_dut_count, no_delay=no_delay)