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..d9f05a115dfbddc32c0631f0e73309aeab9360d1 |
| --- /dev/null |
| +++ b/scripts/master/try_job_gerrit.py |
| @@ -0,0 +1,128 @@ |
| +# 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.schedulers.base import BaseScheduler |
| + |
| +from master.gerrit_poller import GerritPoller |
| + |
| + |
| +class JobDefinition(object): |
|
Vadim Sh.
2014/04/29 01:22:45
Consider using collections.namedtuple + factory fu
nodir
2014/04/29 04:01:00
Decided not to change this because I prefer
1) the
|
| + """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] |
|
Vadim Sh.
2014/04/29 01:22:45
'if b' can probably fit on previous line
also wrap
nodir
2014/04/29 04:01:00
I am not sure it fits the rules
https://engdoc.cor
Vadim Sh.
2014/04/29 18:17:39
Well, I can tell you we are not following this par
|
| + |
| + 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() |
|
Vadim Sh.
2014/04/29 01:22:45
Is it valuable? What happens if it is scheduled?
nodir
2014/04/29 04:01:00
Yes. The default list of builders is used
|
| + |
| + # 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} |
|
Vadim Sh.
2014/04/29 01:22:45
elif not isinstance(job, dict):
raise ValueError
nodir
2014/04/29 04:01:00
Done.
|
| + |
| + 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. |
| + """ |
| + |
| + change_category = 'tryjob' |
| + |
| + MESSAGE_REGEX_TRYJOB = re.compile('Patch set \d+:\s+\!tryjob', re.I) |
|
Vadim Sh.
2014/04/29 01:22:45
Each message contains 'Patch set XXX:' as a header
nodir
2014/04/29 04:01:00
It does
https://quickoffice-internal-review.google
|
| + |
| + def __init__(self, scheduler, gerrit_host, gerrit_projects=None, |
| + pollInterval=None): |
| + assert scheduler |
| + GerritPoller.__init__(self, gerrit_host, gerrit_projects, pollInterval) |
| + self.scheduler = scheduler |
| + |
| + def _is_interesting_comment(self, comment): # pylint: disable=R0201 |
|
Vadim Sh.
2014/04/29 01:22:45
Do you still need this pylint disable here?
nodir
2014/04/29 04:01:00
Done.
|
| + 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' |
| + 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():] |
|
Vadim Sh.
2014/04/29 01:22:45
Use tryjob_match.group(<index>) here instead of .e
nodir
2014/04/29 04:01:00
Done.
|
| + 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) |
| + yield self.scheduler.submitJob(buildbotChange, job) |
| + defer.returnValue(buildbotChange) |
| + except Exception as e: |
| + log.err('TryJobGerritPoller failed: %s' % e) |
|
Vadim Sh.
2014/04/29 01:22:45
Do you need to reraise an exception? Won't caller
nodir
2014/04/29 04:01:00
added raise and tested. This prints a stack trace
|
| + |
| + |
| +class TryJobGerritScheduler(BaseScheduler): |
| + """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. |
| + """ |
| + BaseScheduler.__init__(self, name, |
| + builderNames=default_builder_names, |
| + properties={}) |
| + self.poller = _TryJobGerritPoller(self, gerrit_host, gerrit_projects, |
| + pollInterval) |
| + |
| + def setServiceParent(self, parent): |
| + BaseScheduler.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.""" |
| + |
| + @defer.inlineCallbacks |
| + def submitJob(self, change, job): |
| + bsid = yield self.addBuildsetForChanges( |
| + reason='tryjob', |
| + changeids=[change.number], |
| + builderNames=job.builder_names, |
| + properties=change.properties) |
| + log.msg('Successfully submitted a Gerrit try job for %s: %s.' % |
| + (change.who, job)) |
| + defer.returnValue(bsid) |