OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2014 The Chromium 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 import json |
| 6 import re |
| 7 |
| 8 from twisted.internet import defer |
| 9 from twisted.python import log |
| 10 |
| 11 from buildbot.schedulers.base import BaseScheduler |
| 12 |
| 13 from master.gerrit_poller import GerritPoller |
| 14 |
| 15 |
| 16 class JobDefinition(object): |
| 17 """Describes a try job posted on Gerrit.""" |
| 18 def __init__(self, builder_names=None): |
| 19 # Force str type and remove empty builder names. |
| 20 self.builder_names = [str(b) for b in (builder_names or []) if b] |
| 21 |
| 22 def __repr__(self): |
| 23 return repr(self.__dict__) |
| 24 |
| 25 @staticmethod |
| 26 def parse(text): |
| 27 """Parses a try job definition.""" |
| 28 text = text and text.strip() |
| 29 if not text: |
| 30 # Return an empty definition. |
| 31 return JobDefinition() |
| 32 |
| 33 # Parse as json. |
| 34 try: |
| 35 job = json.loads(text) |
| 36 except: |
| 37 raise ValueError('Couldn\'t parse job definition: %s' % text) |
| 38 |
| 39 # Convert to canonical form. |
| 40 if isinstance(job, list): |
| 41 # Treat a list as builder name list. |
| 42 job = {'builderNames': job} |
| 43 elif not isinstance(job, dict): |
| 44 raise ValueError('Job definition must be a JSON object or array.') |
| 45 |
| 46 return JobDefinition(job.get('builderNames')) |
| 47 |
| 48 |
| 49 class _TryJobGerritPoller(GerritPoller): |
| 50 """Polls issues, creates changes and calls scheduler.submitJob. |
| 51 |
| 52 This class is a part of TryJobGerritScheduler implementation and not designed |
| 53 to be used otherwise. |
| 54 """ |
| 55 |
| 56 change_category = 'tryjob' |
| 57 |
| 58 MESSAGE_REGEX_TRYJOB = re.compile('Patch set \d+:\s+\!tryjob(.*)', re.I) |
| 59 |
| 60 def __init__(self, scheduler, gerrit_host, gerrit_projects=None, |
| 61 pollInterval=None): |
| 62 assert scheduler |
| 63 GerritPoller.__init__(self, gerrit_host, gerrit_projects, pollInterval) |
| 64 self.scheduler = scheduler |
| 65 |
| 66 def _is_interesting_message(self, message): |
| 67 return self.MESSAGE_REGEX_TRYJOB.match(message['message']) |
| 68 |
| 69 def getChangeQuery(self): |
| 70 query = GerritPoller.getChangeQuery(self) |
| 71 # Request only issues with TryJob=+1 label. |
| 72 query += '+label:TryJob=%2B1' |
| 73 return query |
| 74 |
| 75 def parseJob(self, message): |
| 76 """Parses a JobDefinition from a Gerrit message.""" |
| 77 tryjob_match = self.MESSAGE_REGEX_TRYJOB.match(message['message']) |
| 78 assert tryjob_match |
| 79 return JobDefinition.parse(tryjob_match.group(1)) |
| 80 |
| 81 @defer.inlineCallbacks |
| 82 def addChange(self, change, message): |
| 83 """Parses a job, adds a change and calls self.scheduler.submitJob.""" |
| 84 try: |
| 85 job = self.parseJob(message) |
| 86 buildbotChange = yield self.addBuildbotChange(change, message) |
| 87 yield self.scheduler.submitJob(buildbotChange, job) |
| 88 defer.returnValue(buildbotChange) |
| 89 except Exception as e: |
| 90 log.err('TryJobGerritPoller failed: %s' % e) |
| 91 raise |
| 92 |
| 93 |
| 94 class TryJobGerritScheduler(BaseScheduler): |
| 95 """Polls try jobs on Gerrit and creates buildsets.""" |
| 96 def __init__(self, name, default_builder_names, gerrit_host, |
| 97 gerrit_projects=None, pollInterval=None): |
| 98 """Creates a new TryJobGerritScheduler. |
| 99 |
| 100 Args: |
| 101 name: name of the scheduler. |
| 102 default_builder_names: a list of builder names used in case a job didn't |
| 103 specify any. |
| 104 gerrit_host: URL to the Gerrit instance |
| 105 gerrit_projects: Gerrit projects to filter issues. |
| 106 pollInterval: frequency of polling. |
| 107 """ |
| 108 BaseScheduler.__init__(self, name, |
| 109 builderNames=default_builder_names, |
| 110 properties={}) |
| 111 self.poller = _TryJobGerritPoller(self, gerrit_host, gerrit_projects, |
| 112 pollInterval) |
| 113 |
| 114 def setServiceParent(self, parent): |
| 115 BaseScheduler.setServiceParent(self, parent) |
| 116 self.poller.master = self.master |
| 117 self.poller.setServiceParent(self) |
| 118 |
| 119 def gotChange(self, *args, **kwargs): |
| 120 """Do nothing because changes are processed by submitJob.""" |
| 121 |
| 122 @defer.inlineCallbacks |
| 123 def submitJob(self, change, job): |
| 124 bsid = yield self.addBuildsetForChanges( |
| 125 reason='tryjob', |
| 126 changeids=[change.number], |
| 127 builderNames=job.builder_names, |
| 128 properties=change.properties) |
| 129 log.msg('Successfully submitted a Gerrit try job for %s: %s.' % |
| 130 (change.who, job)) |
| 131 defer.returnValue(bsid) |
OLD | NEW |