Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import datetime | 5 import datetime |
| 6 | 6 |
| 7 from buildbot.changes import base | 7 from buildbot.changes import base |
| 8 from buildbot.util import deferredLocked | 8 from buildbot.util import deferredLocked |
| 9 from twisted.python import log | 9 from twisted.python import log |
| 10 from twisted.internet import defer | 10 from twisted.internet import defer |
| 11 | 11 |
| 12 from common.gerrit_agent import GerritAgent | 12 from common.gerrit_agent import GerritAgent |
| 13 | 13 |
| 14 | 14 |
| 15 class GerritPoller(base.PollingChangeSource): | 15 class GerritPoller(base.PollingChangeSource): |
| 16 """A poller which queries a gerrit server for new changes and patchsets.""" | 16 """A poller which queries a gerrit server for new changes and patchsets.""" |
| 17 | 17 |
| 18 change_category = 'patchset-created' | |
| 19 | |
| 18 def __init__(self, gerrit_host, gerrit_projects=None, pollInterval=None): | 20 def __init__(self, gerrit_host, gerrit_projects=None, pollInterval=None): |
| 19 if isinstance(gerrit_projects, basestring): | 21 if isinstance(gerrit_projects, basestring): |
| 20 gerrit_projects = [gerrit_projects] | 22 gerrit_projects = [gerrit_projects] |
| 21 self.gerrit_projects = gerrit_projects | 23 self.gerrit_projects = gerrit_projects |
| 22 if pollInterval: | 24 if pollInterval: |
| 23 self.pollInterval = pollInterval | 25 self.pollInterval = pollInterval |
| 24 self.initLock = defer.DeferredLock() | 26 self.initLock = defer.DeferredLock() |
| 25 self.last_timestamp = None | 27 self.last_timestamp = None |
| 26 self.agent = GerritAgent(gerrit_host) | 28 self.agent = GerritAgent(gerrit_host) |
| 27 | 29 |
| 28 @staticmethod | 30 @staticmethod |
| 29 def _parse_timestamp(tm): | 31 def _parse_timestamp(tm): |
| 30 tm = tm[:tm.index('.')+7] | 32 tm = tm[:tm.index('.')+7] |
| 31 return datetime.datetime.strptime(tm, '%Y-%m-%d %H:%M:%S.%f') | 33 return datetime.datetime.strptime(tm, '%Y-%m-%d %H:%M:%S.%f') |
| 32 | 34 |
| 33 def startService(self): | 35 def startService(self): |
| 34 self.initLastTimeStamp() | 36 self.initLastTimeStamp() |
| 35 base.PollingChangeSource.startService(self) | 37 base.PollingChangeSource.startService(self) |
| 36 | 38 |
| 39 def getChangeQuery(self): # pylint: disable=R0201 | |
| 40 return 'status:open' | |
| 41 | |
| 37 @deferredLocked('initLock') | 42 @deferredLocked('initLock') |
| 38 def initLastTimeStamp(self): | 43 def initLastTimeStamp(self): |
| 39 log.msg('GerritPoller: Getting latest timestamp from gerrit server.') | 44 log.msg('GerritPoller: Getting latest timestamp from gerrit server.') |
| 40 path = '/changes/?q=status:open&n=1' | 45 path = '/changes/?q=%s&n=1' % self.getChangeQuery() |
|
Vadim Sh.
2014/04/29 01:22:45
So if try server was offline and brought back onli
nodir
2014/04/29 04:01:00
I've intentionally didn't improve this part much b
| |
| 41 d = self.agent.request('GET', path) | 46 d = self.agent.request('GET', path) |
| 42 def _get_timestamp(j): | 47 def _get_timestamp(j): |
| 43 if len(j) == 0: | 48 if len(j) == 0: |
| 44 self.last_timestamp = datetime.datetime.now() | 49 self.last_timestamp = datetime.datetime.now() |
| 45 else: | 50 else: |
| 46 self.last_timestamp = self._parse_timestamp(j[0]['updated']) | 51 self.last_timestamp = self._parse_timestamp(j[0]['updated']) |
| 47 d.addCallback(_get_timestamp) | 52 d.addCallback(_get_timestamp) |
| 48 return d | 53 return d |
| 49 | 54 |
| 50 def getChanges(self, sortkey=None): | 55 def getChanges(self, sortkey=None): |
| 51 path = '/changes/?q=status:open&n=10' | 56 path = '/changes/?q=%s&n=10' % self.getChangeQuery() |
| 52 if sortkey: | 57 if sortkey: |
| 53 path += '&N=%s' % sortkey | 58 path += '&N=%s' % sortkey |
| 54 return self.agent.request('GET', path) | 59 return self.agent.request('GET', path) |
| 55 | 60 |
| 61 def _is_interesting_comment(self, comment): # pylint: disable=R0201 | |
|
Vadim Sh.
2014/04/29 01:22:45
Let's follow Gerrit REST API and call that entity
nodir
2014/04/29 04:01:00
Done.
| |
| 62 return comment['message'].startswith('Uploaded patch set ') | |
| 63 | |
| 56 def checkForNewPatchset(self, change, since): | 64 def checkForNewPatchset(self, change, since): |
| 57 o_params = '&'.join('o=%s' % x for x in ( | 65 o_params = '&'.join('o=%s' % x for x in ( |
| 58 'MESSAGES', 'CURRENT_REVISION', 'CURRENT_COMMIT', 'ALL_FILES')) | 66 'MESSAGES', 'CURRENT_REVISION', 'CURRENT_COMMIT', 'ALL_FILES')) |
| 59 path = '/changes/%s?%s' % (change['_number'], o_params) | 67 path = '/changes/%s?%s' % (change['_number'], o_params) |
| 60 d = self.agent.request('GET', path) | 68 d = self.agent.request('GET', path) |
| 61 def _parse_messages(j): | 69 def _parse_messages(j): |
| 62 if not j or 'messages' not in j: | 70 if not j or 'messages' not in j: |
| 63 return | 71 return |
| 64 for m in reversed(j['messages']): | 72 for m in reversed(j['messages']): |
| 65 if self._parse_timestamp(m['date']) <= since: | 73 if self._parse_timestamp(m['date']) <= since: |
| 66 break | 74 break |
| 67 if m['message'].startswith('Uploaded patch set '): | 75 if self._is_interesting_comment(m): |
| 68 return j | 76 return j, m |
| 69 d.addCallback(_parse_messages) | 77 d.addCallback(_parse_messages) |
| 70 return d | 78 return d |
| 71 | 79 |
| 72 def createBuildbotChange(self, change): | 80 def addBuildbotChange(self, change, comment): |
| 73 revision = change['revisions'].values()[0] | 81 revision = change['revisions'].values()[0] |
| 74 commit = revision['commit'] | 82 commit = revision['commit'] |
| 75 properties = {'event.change.number': change['_number']} | 83 properties = {'event.change.number': change['_number']} |
| 76 if change['status'] == 'NEW': | 84 if change['status'] == 'NEW': |
| 77 ref = revision.get('fetch', {}).get('http', {}).get('ref') | 85 ref = revision.get('fetch', {}).get('http', {}).get('ref') |
| 78 if ref: | 86 if ref: |
| 79 properties['event.patchSet.ref'] = ref | 87 properties['event.patchSet.ref'] = ref |
| 80 elif change['status'] in ('SUBMITTED', 'MERGED'): | 88 elif change['status'] in ('SUBMITTED', 'MERGED'): |
| 81 properties['event.refUpdate.newRev'] = change['current_revision'] | 89 properties['event.refUpdate.newRev'] = change['current_revision'] |
| 82 chdict = { | 90 chdict = { |
| 83 'author': '%s <%s>' % ( | 91 'author': '%s <%s>' % ( |
| 84 commit['author']['name'], commit['author']['email']), | 92 commit['author']['name'], commit['author']['email']), |
| 85 'project': change['project'], | 93 'project': change['project'], |
| 86 'branch': change['branch'], | 94 'branch': change['branch'], |
| 87 'revision': change['current_revision'], | 95 'revision': change['current_revision'], |
| 88 'comments': commit['subject'], | 96 'comments': commit['subject'], |
| 89 'files': commit['files'].keys() if 'files' in commit else ['UNKNOWN'], | 97 'files': commit['files'].keys() if 'files' in commit else ['UNKNOWN'], |
| 90 'category': 'patchset-created', | 98 'category': self.change_category, |
| 91 'when_timestamp': self._parse_timestamp(commit['committer']['date']), | 99 'when_timestamp': self._parse_timestamp(commit['committer']['date']), |
| 92 'revlink': '%s://%s/#/c/%s' % ( | 100 'revlink': '%s://%s/#/c/%s' % ( |
| 93 self.agent.gerrit_protocol, self.agent.gerrit_host, | 101 self.agent.gerrit_protocol, self.agent.gerrit_host, |
| 94 change['_number']), | 102 change['_number']), |
| 95 'repository': '%s://%s/%s' % ( | 103 'repository': '%s://%s/%s' % ( |
| 96 self.agent.gerrit_protocol, self.agent.gerrit_host, | 104 self.agent.gerrit_protocol, self.agent.gerrit_host, |
| 97 change['project']), | 105 change['project']), |
| 98 'properties': properties} | 106 'properties': properties, |
| 107 } | |
| 99 d = self.master.addChange(**chdict) | 108 d = self.master.addChange(**chdict) |
| 100 d.addErrback(log.err, 'GerritPoller: Could not add buildbot change for ' | 109 d.addErrback(log.err, 'GerritPoller: Could not add buildbot change for ' |
| 101 'gerrit change %s.' % revision['_number']) | 110 'gerrit change %s.' % revision['_number']) |
| 102 return d | 111 return d |
| 103 | 112 |
| 113 def addChange(self, (change, comment)): | |
|
Vadim Sh.
2014/04/29 01:22:45
I'd prefer we not use structured args. That's unus
nodir
2014/04/29 04:01:00
Done
| |
| 114 return self.addBuildbotChange(change, comment) | |
| 115 | |
| 104 def processChanges(self, j, since): | 116 def processChanges(self, j, since): |
|
Vadim Sh.
2014/04/29 01:22:45
Somehow I can't shake a feeling this code has subt
nodir
2014/04/29 04:01:00
This will be also change with the new GerritAgent
| |
| 105 need_more = bool(j) | 117 need_more = bool(j) |
| 106 for change in j: | 118 for change in j: |
| 107 tm = self._parse_timestamp(change['updated']) | 119 tm = self._parse_timestamp(change['updated']) |
| 108 if tm <= since: | 120 if tm <= since: |
| 109 need_more = False | 121 need_more = False |
| 110 break | 122 break |
| 111 if self.gerrit_projects and change['project'] not in self.gerrit_projects: | 123 if self.gerrit_projects and change['project'] not in self.gerrit_projects: |
| 112 continue | 124 continue |
| 113 d = self.checkForNewPatchset(change, since) | 125 d = self.checkForNewPatchset(change, since) |
| 114 d.addCallback(lambda x: self.createBuildbotChange(x) if x else None) | 126 d.addCallback(lambda x: self.addChange(x) if x else None) |
| 115 if need_more and j[-1].get('_more_changes'): | 127 if need_more and j[-1].get('_more_changes'): |
| 116 d = self.getChanges(sortkey=j[-1]['_sortkey']) | 128 d = self.getChanges(sortkey=j[-1]['_sortkey']) |
| 117 d.addCallback(self.processChanges, since=since) | 129 d.addCallback(self.processChanges, since=since) |
| 118 else: | 130 else: |
| 119 d = defer.succeed(None) | 131 d = defer.succeed(None) |
| 120 return d | 132 return d |
| 121 | 133 |
| 122 @deferredLocked('initLock') | 134 @deferredLocked('initLock') |
| 123 def poll(self): | 135 def poll(self): |
| 124 log.msg('GerritPoller: getting latest changes...') | 136 log.msg('GerritPoller: getting latest changes...') |
| 125 since = self.last_timestamp | 137 since = self.last_timestamp |
| 126 d = self.getChanges() | 138 d = self.getChanges() |
| 127 def _update_last_timestamp(j): | 139 def _update_last_timestamp(j): |
| 128 if j: | 140 if j: |
| 129 self.last_timestamp = self._parse_timestamp(j[0]['updated']) | 141 self.last_timestamp = self._parse_timestamp(j[0]['updated']) |
| 130 return j | 142 return j |
| 131 d.addCallback(_update_last_timestamp) | 143 d.addCallback(_update_last_timestamp) |
| 132 d.addCallback(self.processChanges, since=since) | 144 d.addCallback(self.processChanges, since=since) |
| 133 return d | 145 return d |
| OLD | NEW |