Example #1
0
    def SubmitJob(self, parsed_job, changeids):
        if not parsed_job['bot']:
            raise BadJobfile(
                'incoming Try job did not specify any allowed builder names')

        # Verify the try job patch is not more than 20MB.
        if parsed_job.get('patch'):
            patchsize = len(parsed_job['patch'])
            if patchsize > 20 * 1024 * 1024:  # 20MB
                raise BadJobfile('incoming Try job patch is %s bytes, '
                                 'must be less than 20MB' % (patchsize))

        d = self.master.db.sourcestamps.addSourceStamp(
            branch=parsed_job['branch'],
            revision=parsed_job['revision'],
            patch_body=parsed_job['patch'],
            patch_level=parsed_job['patchlevel'],
            patch_subdir=parsed_job['root'],
            project=parsed_job['project'],
            repository=parsed_job['repository'] or '',
            changeids=changeids)

        d.addCallback(self.create_buildset, parsed_job)
        d.addErrback(log.err, "Failed to queue a try job!")
        return d
Example #2
0
def parse_options(options, builders, pools):
    """Converts try job settings into a dict.

  The dict is received as dict(str, list(str)).
  """
    try:
        # Flush empty values.
        options = dict((k, v) for k, v in options.iteritems() if v)

        flatten(options, 'name', 'Unnamed')
        flatten(options, 'user', 'John Doe')

        comma_separated(options, 'email')
        for email in options['email']:
            # Access to a protected member XXX of a client class
            # pylint: disable=W0212
            if not TryJobBase._EMAIL_VALIDATOR.match(email):
                raise BadJobfile("'%s' is an invalid email address!" % email)

        flatten(options, 'patch', None)
        flatten(options, 'root', None)
        try_int(options, 'patchlevel', 0)
        flatten(options, 'branch', None)
        flatten(options, 'revision', None)
        flatten(options, 'reason',
                '%s: %s' % (options['user'], options['name']))
        try_bool(options, 'clobber', False)

        flatten(options, 'project', pools.default_pool_name if pools else None)
        flatten(options, 'repository', None)

        # Code review info. Enforce numbers.
        try_int(options, 'patchset', None)
        try_int(options, 'issue', None)

        # Manages bot selection and test filtering. It is preferable to use
        # multiple bot=unit_tests:Gtest.Filter lines. DEFAULT_TESTS is a marker to
        # specify that the default tests should be run for this builder.
        options['bot'] = dict_comma(options.get('bot', []), builders,
                                    DEFAULT_TESTS)
        if not options['bot'] and pools:
            options['bot'] = pools.Select(None, options['project'])
        if not options['bot']:
            raise BadJobfile('No builder selected')
        comma_separated(options, 'testfilter')
        if options['testfilter']:
            for k in options['bot']:
                options['bot'][k].update(options['testfilter'])
        # Convert the set back to list.
        for bot in options['bot']:
            options['bot'][bot] = list(options['bot'][bot])

        log.msg('Chose %s for job %s' %
                (','.join(options['bot']), options['reason']))
        return options
    except (TypeError, ValueError), e:
        lines = [(i[0].rsplit('/', 1)[-1], i[1])
                 for i in traceback.extract_tb(sys.exc_info()[2])]
        raise BadJobfile('Failed to parse the metadata: %r' % options, e,
                         lines)
Example #3
0
def dict_comma(values, valid_keys, default):
    """Splits comma separated strings with : to create a dict."""
    out = {}
    # We accept two formats:
    # 1. Builder(s) only, will run default tests.  Eg. win,mac
    # 2. Builder(s) with test(s): Eg. win,mac:test1:filter1,test2:filter2,test3
    #    In this example, win will run default tests, and mac will be ran with
    #    test1:filter1, test2:filter2, and test3.
    # In no cases are builders allowed to be specified after tests are specified.

    for value in values:
        sections_match = re.match(r'^([^\:]+):?(.*)$', value)
        if sections_match:
            builders_raw, tests_raw = sections_match.groups()
            builders = builders_raw.split(',')
            tests = [test for test in tests_raw.split(',') if test]
            for builder in builders:
                if not builder:
                    # Empty builder means trailing comma.
                    raise BadJobfile('Bad input - trailing comma: %s' % value)
                if builder not in valid_keys:
                    # Drop unrecognized builders.
                    log.msg('%s not in builders, dropping.' % builder)
                    continue
                if not tests or builder != builders[-1]:
                    # We can only add filters to the last builder.
                    out.setdefault(builder, set()).add(default)
                else:
                    for test in tests:
                        if len(test.split(':')) > 2:
                            # Don't support test:filter:foo.
                            raise BadJobfile('Failed to process test %s' %
                                             value)
                        out.setdefault(builder, set()).add(test)
        else:
            raise BadJobfile('Failed to process value %s' % value)

    return out
    def SubmitJobs(self, jobs):
        """Submit pending try jobs to slaves for building.

    Args:
      jobs: a list of jobs.  Each job is a dictionary of properties describing
          what to build.
    """
        log.msg('TryJobRietveld.SubmitJobs: %s' % json.dumps(jobs, indent=2))
        for job in jobs:
            try:
                # Gate the try job on the user that requested the job, not the one that
                # authored the CL.
                if not self._valid_users.contains(job['requester']):
                    raise BadJobfile('TryJobRietveld rejecting job from %s' %
                                     job['requester'])

                if job['email'] != job['requester']:
                    # Note the fact the try job was requested by someone else in the
                    # 'reason'.
                    job['reason'] = job.get('reason') or ''
                    if job['reason']:
                        job['reason'] += '; '
                    job['reason'] += "This CL was triggered by %s" % job[
                        'requester']

                options = {
                    'bot': {
                        job['builder']: job['tests']
                    },
                    'email': [job['email']],
                    'project': [self._project],
                    'try_job_key': job['key'],
                }
                # Transform some properties as is expected by parse_options().
                for key in ('name', 'user', 'root', 'reason', 'clobber',
                            'patchset', 'issue', 'requester', 'revision'):
                    options[key] = [job[key]]

                # Now cleanup the job dictionary and submit it.
                cleaned_job = self.parse_options(options)

                yield self.get_lkgr(cleaned_job)
                c = yield self.master.addChange(
                    author=','.join(cleaned_job['email']),
                    # TODO(maruel): Get patchset properties to get the list of files.
                    # files=[],
                    revision=cleaned_job['revision'],
                    comments='')
                changeids = [c.number]

                cleaned_job['patch_storage'] = 'rietveld'
                yield self.SubmitJob(cleaned_job, changeids)
            except BadJobfile, e:
                # We need to mark it as failed otherwise it'll stay in the pending
                # state. Simulate a buildFinished event on the build.
                log.err('Got "%s" for issue %s' % (e, job.get('issue')))
                for service in self.master.services:
                    if service.__class__.__name__ == 'TryServerHttpStatusPush':
                        build = {
                            'properties': [
                                ('buildername', job.get('builder'), None),
                                ('buildnumber', -1, None),
                                ('issue', job['issue'], None),
                                ('patchset', job['patchset'], None),
                                ('project', self._project, None),
                                ('revision', '', None),
                                ('slavename', '', None),
                                ('try_job_key', job['key'], None),
                            ],
                            'reason':
                            job.get('reason', ''),
                            # Use EXCEPTION until SKIPPED results in a non-green try job
                            # results on Rietveld.
                            'results':
                            EXCEPTION,
                        }
                        service.push('buildFinished', build=build)
                        log.err('Rietveld updated')
                        break
                else:
                    log.err(
                        'Rietveld not updated: no corresponding service found.'
                    )
Example #5
0
def dict_comma(values, valid_keys, default):
    """Splits comma separated strings with : to create a dict."""
    out = {}
    # See the unit test to understand all the supported formats. It's insane
    # for now.
    for value in values:
        # Slowly process the line as a state machine.
        # The inputs are:
        #  A. The next symbol, a string and the one after.
        #  B. The separator after: (',', ':', None).
        #  C. The last processed bot name.
        #  D. The last processed test name.
        # There is 4 states:
        #  1. Next item must be a bot: value='builder'. Next state can be 1 or 2.
        #  2. Next item must be a test: value='builder:test' when the index is at
        #     test. Next state can be 3 or 4.
        #  3. Next item must be a test filter: builder:test:filter when the index is
        #     at filter. Next state is 4.
        #  4. Next item can be a bot or is a test: value='builder:test,not_sure' or
        #     value='builder:test:filter,not_sure' when the index is at not_sure. It
        #     depends on not_sure being in valid_keys or not. Next state is 2 or 4.
        items = re.split(r'([\,\:])', value)
        if not items or ((len(items) - 1) % 2) != 0:
            raise BadJobfile('Failed to process value %s', value)

        last_key = None  # A bot
        last_value = None  # A test
        state = 1  # The initial item is a last_key (bot).

        while items:
            # Eat two items at a time:
            item = items.pop(0)
            separator = items.pop(
                0) if items else None  # Technically, next_separator

            # Refuse "foo,:bar"
            if item in (',', ':') or separator not in (',', ':', None):
                raise BadJobfile('Failed to process value %s', value)

            if state == 1:
                assert last_key is None
                assert last_value is None
                if item not in valid_keys:
                    raise BadJobfile('Failed to process value %s', value)
                if separator == ':':
                    # Don't save it yet.
                    last_key = item
                    state = 2
                else:
                    out.setdefault(item, set()).add(default)
                    state = 1

            elif state == 2:
                assert last_key is not None
                assert last_value is None
                if separator == ':':
                    last_value = item
                    state = 3
                else:
                    out.setdefault(last_key, set()).add(item)
                    last_value = None
                    state = 4

            elif state == 3:
                assert last_key is not None
                assert last_value is not None
                if separator == ':':
                    raise BadJobfile('Failed to process value %s', value)
                out.setdefault(last_key,
                               set()).add('%s:%s' % (last_value, item))
                last_value = None
                state = 4

            elif state == 4:
                assert last_key is not None
                assert last_value is None
                if separator == ':':
                    if item not in valid_keys:
                        last_value = item
                        state = 3
                    else:
                        last_key = item
                        state = 2
                else:
                    if item not in valid_keys:
                        # A value.
                        out.setdefault(last_key, set()).add(item)
                    else:
                        # A key.
                        out.setdefault(item, set()).add(default)

    return out
Example #6
0
def TryJobRietveldSubmitJobs(self, jobs):
    """ Override of master.try_job_rietveld.TryJobRietveld.SubmitJobs:
  http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/master/try_job_rietveld.py?view=markup

  We modify it to include "baseurl" as a build property.
  """
    log.msg('TryJobRietveld.SubmitJobs: %s' % json.dumps(jobs, indent=2))
    for job in jobs:
        try:
            # Gate the try job on the user that requested the job, not the one that
            # authored the CL.
            # pylint: disable=W0212
            ########################## Added by rmistry ##########################
            if (job.get('requester')
                    and not job['requester'].endswith('@google.com')
                    and not job['requester'].endswith('@chromium.org')
                    and not job['requester'] in TRYBOTS_REQUESTER_WHITELIST):
                # Reject the job only if the requester has an email not ending in
                # google.com or chromium.org
                raise BadJobfile('TryJobRietveld rejecting job from %s' %
                                 job['requester'])
            ######################################################################
            ########################## Added by borenet ##########################
            if not (job.get('baseurl')
                    and config_private.Master.Skia.project_name.lower()
                    in job['baseurl']):
                raise BadJobfile(
                    'TryJobRietveld rejecting job with unknown baseurl: %s' %
                    job.get('baseurl'))
            ######################################################################
            if job['email'] != job['requester']:
                # Note the fact the try job was requested by someone else in the
                # 'reason'.
                job['reason'] = job.get('reason') or ''
                if job['reason']:
                    job['reason'] += '; '
                job['reason'] += "This CL was triggered by %s" % job[
                    'requester']

            options = {
                'bot': {
                    job['builder']: job['tests']
                },
                'email': [job['email']],
                'project': [self._project],
                'try_job_key': job['key'],
            }
            # Transform some properties as is expected by parse_options().
            for key in (
                    ########################## Added by borenet ##########################
                    'baseurl',
                    ######################################################################
                    'name',
                    'user',
                    'root',
                    'reason',
                    'clobber',
                    'patchset',
                    'issue',
                    'requester',
                    'revision'):
                options[key] = [job[key]]

            # Now cleanup the job dictionary and submit it.
            cleaned_job = self.parse_options(options)

            wfd = defer.waitForDeferred(self.get_lkgr(cleaned_job))
            yield wfd
            wfd.getResult()

            wfd = defer.waitForDeferred(
                self.master.addChange(
                    author=','.join(cleaned_job['email']),
                    # TODO(maruel): Get patchset properties to get the list of files.
                    # files=[],
                    revision=cleaned_job['revision'],
                    comments=''))
            yield wfd
            changeids = [wfd.getResult().number]

            wfd = defer.waitForDeferred(self.SubmitJob(cleaned_job, changeids))
            yield wfd
            wfd.getResult()
        except BadJobfile, e:
            # We need to mark it as failed otherwise it'll stay in the pending
            # state. Simulate a buildFinished event on the build.
            if not job.get('key'):
                log.err(
                    'Got %s for issue %s but not key, not updating Rietveld' %
                    (e, job.get('issue')))
                continue
            log.err('Got %s for issue %s, updating Rietveld' %
                    (e, job.get('issue')))
            for service in self.master.services:
                if service.__class__.__name__ == 'TryServerHttpStatusPush':
                    # pylint: disable=W0212,W0612
                    build = {
                        'properties': [
                            ('buildername', job.get('builder'), None),
                            ('buildnumber', -1, None),
                            ('issue', job['issue'], None),
                            ('patchset', job['patchset'], None),
                            ('project', self._project, None),
                            ('revision', '', None),
                            ('slavename', '', None),
                            ('try_job_key', job['key'], None),
                        ],
                        'reason':
                        job.get('reason', ''),
                        # Use EXCEPTION until SKIPPED results in a non-green try job
                        # results on Rietveld.
                        'results':
                        EXCEPTION,
                    }
                    ########################## Added by rmistry #########################
                    # Do not update Rietveld to mark the try job request as failed.
                    # See https://code.google.com/p/chromium/issues/detail?id=224014 for
                    # more context.
                    # service.push('buildFinished', build=build)
                    #####################################################################
                    break