Chromium Code Reviews| Index: scripts/master/try_job_gerrit.py |
| diff --git a/scripts/master/try_job_gerrit.py b/scripts/master/try_job_gerrit.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..60e24755a81f2bcf56cac794650e2716a898cdc7 |
| --- /dev/null |
| +++ b/scripts/master/try_job_gerrit.py |
| @@ -0,0 +1,141 @@ |
| +# Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +import json |
| +import re |
| + |
| +from twisted.internet import defer |
| +from twisted.python import log |
| + |
| +from buildbot.changes import filter |
| +from buildbot.schedulers.basic import SingleBranchScheduler |
| + |
| +from master.gerrit_poller import GerritPoller |
| +from master.builders_pools import BuildersPools |
| + |
| + |
| +ALWAYS_TRUE_FILTER = filter.ChangeFilter(filter_fn=lambda x: True) |
| + |
| + |
| +class JobDefinition(object): |
| + """Describes a try job posted on Gerrit.""" |
| + def __init__(self, builder_names=None): |
| + # Force str type and remove empty builder names. |
| + self.builder_names = [str(b) for b in builder_names or [] |
| + if b] |
| + |
| + def __repr__(self): |
| + return repr(self.__dict__) |
| + |
| + @staticmethod |
| + def parse(text): |
| + """Parses a try job definition.""" |
| + text = text and text.strip() |
| + if not text: |
| + # Return an empty definition. |
| + return JobDefinition() |
| + |
| + # Parse as json. |
| + job = json.loads(text) |
| + |
| + # Convert to canonical form. |
| + if isinstance(job, list): |
| + # Treat a list as builder name list. |
| + job = {'builderNames': job} |
| + |
| + return JobDefinition(job.get('builderNames')) |
| + |
| + |
| +class _TryJobGerritPoller(GerritPoller): |
| + """Polls issues, creates changes and calls scheduler.submitJob. |
| + |
| + This class is a part of TryJobGerritScheduler implementation and not designed |
| + to be used otherwise. |
| + """ |
| + |
| + MESSAGE_REGEX_TRYJOB = re.compile('Patch set \d+:\s+\!tryjob', re.I) |
| + |
| + def __init__(self, scheduler, gerrit_host, gerrit_projects=None, |
| + pollInterval=None): |
| + assert scheduler |
| + GerritPoller.__init__(self, gerrit_host, gerrit_projects, pollInterval) |
| + self.scheduler = scheduler |
|
Dan Jacques
2014/04/26 00:38:51
I don't think the 'change source' / 'scheduler' se
nodir
2014/04/28 22:31:11
In general, yes, a poller and scheduler don't know
|
| + |
| + def _is_interesting_comment(self, comment): |
| + return self.MESSAGE_REGEX_TRYJOB.match(comment['message']) |
| + |
| + def getChangeQuery(self): |
| + query = GerritPoller.getChangeQuery(self) |
| + # Request only issues with TryJob=+1 label. |
| + query += '+label:TryJob=%2B1' |
|
Dan Jacques
2014/04/26 00:38:51
While you're constraining the query, you might as
nodir
2014/04/28 22:31:11
I think I will wait for your GerritAgent
|
| + return query |
| + |
| + def parseJob(self, comment): |
| + """Parses a JobDefinition from a Gerrit comment.""" |
| + msg = comment['message'] |
| + tryjob_match = self.MESSAGE_REGEX_TRYJOB.match(msg) |
| + assert tryjob_match |
| + job_def_str = msg[tryjob_match.end():] |
| + return JobDefinition.parse(job_def_str) |
| + |
| + @defer.inlineCallbacks |
| + def addChange(self, (change, comment)): |
| + """Parses a job, adds a change and calls self.scheduler.submitJob.""" |
| + try: |
| + job = self.parseJob(comment) |
| + buildbotChange = yield self.addBuildbotChange(change, comment) |
| + self.scheduler.submitJob(buildbotChange, job) |
|
Dan Jacques
2014/04/26 00:38:51
This needs to be yielded, doesn't it?
nodir
2014/04/28 22:31:11
Done.
|
| + except Exception as e: |
| + log.err('TryJobGerritPoller failed: %s' % e) |
| + |
| + |
| +class TryJobGerritScheduler(SingleBranchScheduler): |
|
Dan Jacques
2014/04/26 00:38:51
It would be nice if you could use a less heavy-han
nodir
2014/04/28 22:31:11
This scheduler takes the builder names from the jo
|
| + """Polls try jobs on Gerrit and creates buildsets.""" |
| + def __init__(self, name, default_builder_names, gerrit_host, |
| + gerrit_projects=None, pollInterval=None): |
| + """Creates a new TryJobGerritScheduler. |
| + |
| + Args: |
| + name: name of the scheduler. |
| + default_builder_names: a list of builder names used in case a job didn't |
| + specify any. |
| + gerrit_host: URL to the Gerrit instance |
| + gerrit_projects: Gerrit projects to filter issues. |
| + pollInterval: frequency of polling. |
| + """ |
| + SingleBranchScheduler.__init__(self, name, |
| + builderNames=default_builder_names, |
|
Kevin Graney
2014/04/26 02:45:11
Are you somehow running PRESUBMIT.py for the jobs
nodir
2014/04/28 22:31:11
This code lives on server. The git-try will run PR
|
| + change_filter=ALWAYS_TRUE_FILTER) |
| + self.poller = _TryJobGerritPoller(self, gerrit_host, gerrit_projects, |
| + pollInterval) |
| + |
| + def setServiceParent(self, parent): |
| + SingleBranchScheduler.setServiceParent(self, parent) |
| + self.poller.master = self.master |
| + self.poller.setServiceParent(self) |
| + |
| + def gotChange(self, *args, **kwargs): |
| + """Do nothing because changes are processed by submitJob.""" |
| + |
| + def addSourcestamp(self, change): |
| + return self.master.db.sourcestamps.addSourceStamp( |
| + project=change.project, |
| + repository=change.repository, |
| + branch=change.branch, |
| + revision=change.revision) |
| + |
| + def addBuildset(self, change, ssid, job): |
| + return self.addBuildsetForSourceStamp( |
| + ssid=ssid, |
| + reason='tryjob', |
| + properties=change.properties, |
| + builderNames=job.builder_names) |
| + |
| + @defer.inlineCallbacks |
| + def submitJob(self, change, job): |
| + ssid = yield self.addSourcestamp(change) |
| + bsid = yield self.addBuildset(change, ssid, job) |
| + log.msg('Successfully submitted a Gerrit try job for %s: %s.' % (change.who, |
| + job)) |
| + defer.returnValue(bsid) |