Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 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 base64 | |
| 5 import json | 6 import json |
| 6 import os | 7 import os |
| 7 import re | 8 import re |
| 8 import shutil | 9 import shutil |
| 9 | 10 |
| 10 from StringIO import StringIO | 11 from StringIO import StringIO |
| 11 | 12 |
| 12 try: | 13 try: |
| 13 # Create a block to work around evil sys.modules manipulation in | 14 # Create a block to work around evil sys.modules manipulation in |
| 14 # email/__init__.py that triggers pylint false positives. | 15 # email/__init__.py that triggers pylint false positives. |
| 15 # pylint: disable=E0611,F0401 | 16 # pylint: disable=E0611,F0401 |
| 16 from email.Message import Message | 17 from email.Message import Message |
| 17 from email.Utils import formatdate | 18 from email.Utils import formatdate |
| 18 except ImportError: | 19 except ImportError: |
| 19 raise | 20 raise |
| 20 | 21 |
| 21 from buildbot.process.properties import Properties | 22 from buildbot.process.properties import Properties |
| 22 from buildbot.schedulers.trysched import TryBase | 23 from buildbot.schedulers.trysched import TryBase |
| 23 | 24 |
| 24 from twisted.internet import defer, reactor, utils | 25 from twisted.internet import defer, reactor, utils |
| 25 from twisted.mail.smtp import SMTPSenderFactory | 26 from twisted.mail.smtp import SMTPSenderFactory |
| 26 from twisted.python import log | 27 from twisted.python import log |
| 27 | 28 |
| 29 from common.twisted_util.response import StringResponse | |
| 30 from master import gitiles_poller | |
| 28 from master.try_job_base import BadJobfile | 31 from master.try_job_base import BadJobfile |
| 29 | 32 |
| 30 | 33 |
| 31 class InvalidEtcBuild(BadJobfile): | 34 class InvalidEtcBuild(BadJobfile): |
| 32 pass | 35 pass |
| 33 | 36 |
| 34 | 37 |
| 35 def get_file_contents(poller, branch, file_path): | |
| 36 """Returns a Deferred to returns the file's content.""" | |
| 37 return utils.getProcessOutput( | |
| 38 poller.gitbin, | |
| 39 ['show', 'origin/%s:%s' % (branch, file_path)], | |
| 40 path=poller.workdir, | |
| 41 ) | |
| 42 | |
| 43 | |
| 44 def translate_v1_to_v2(parsed_job): | 38 def translate_v1_to_v2(parsed_job): |
| 45 """Translate tryjob desc from V1 to V2.""" | 39 """Translate tryjob desc from V1 to V2.""" |
| 46 parsed_job.setdefault('extra_args', []).append('--remote-trybot') | 40 parsed_job.setdefault('extra_args', []).append('--remote-trybot') |
| 47 parsed_job['version'] = 2 | 41 parsed_job['version'] = 2 |
| 48 | 42 |
| 49 | 43 |
| 50 def translate_v2_to_v3(parsed_job): | 44 def translate_v2_to_v3(parsed_job): |
| 51 """Translate tryjob desc from V2 to V3.""" | 45 """Translate tryjob desc from V2 to V3.""" |
| 52 # V3 --remote-patches format is not backwards compatible. | 46 # V3 --remote-patches format is not backwards compatible. |
| 53 if any(a.startswith('--remote-patches') | 47 if any(a.startswith('--remote-patches') |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 69 _TRANSLATION_FUNCS = { | 63 _TRANSLATION_FUNCS = { |
| 70 1 : translate_v1_to_v2, | 64 1 : translate_v1_to_v2, |
| 71 2 : translate_v2_to_v3, | 65 2 : translate_v2_to_v3, |
| 72 } | 66 } |
| 73 | 67 |
| 74 # Valid 'etc' builder targets. Specifically, this ensures: | 68 # Valid 'etc' builder targets. Specifically, this ensures: |
| 75 # - The build name doesn't begin with a flag ('--') | 69 # - The build name doesn't begin with a flag ('--') |
| 76 # - The build name doesn't contain spaces (to spill into extra args). | 70 # - The build name doesn't contain spaces (to spill into extra args). |
| 77 ETC_TARGET_RE = re.compile(r'^[a-zA-Z][\w-]+\w$') | 71 ETC_TARGET_RE = re.compile(r'^[a-zA-Z][\w-]+\w$') |
| 78 | 72 |
| 73 # Template path URL component to retrieve the Base64 contents of a file from | |
| 74 # Gitiles. | |
| 75 _GITILES_PATH_TMPL = '%(repo)s/+/%(revision)s/%(path)s?format=text' | |
| 76 | |
| 79 @classmethod | 77 @classmethod |
| 80 def updateJobDesc(cls, parsed_job): | 78 def updateJobDesc(cls, parsed_job): |
| 81 """Ensure job description is in the format we expect.""" | 79 """Ensure job description is in the format we expect.""" |
| 82 while parsed_job['version'] < cls._TRYJOB_FORMAT_VERSION: | 80 while parsed_job['version'] < cls._TRYJOB_FORMAT_VERSION: |
| 83 prev_ver = parsed_job['version'] | 81 prev_ver = parsed_job['version'] |
| 84 translation_func = cls._TRANSLATION_FUNCS[parsed_job['version']] | 82 translation_func = cls._TRANSLATION_FUNCS[parsed_job['version']] |
| 85 translation_func(parsed_job) | 83 translation_func(parsed_job) |
| 86 if parsed_job['version'] <= prev_ver: | 84 if parsed_job['version'] <= prev_ver: |
| 87 raise AssertionError('translation function %s not incrementing version!' | 85 raise AssertionError('translation function %s not incrementing version!' |
| 88 % str(translation_func)) | 86 % str(translation_func)) |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 192 # Calculate the buildroot for builds. | 190 # Calculate the buildroot for builds. |
| 193 config_dict = self.cbuildbot_configs.get(config) | 191 config_dict = self.cbuildbot_configs.get(config) |
| 194 if config_dict is None: | 192 if config_dict is None: |
| 195 buildroot = '/b/cbuild/etc_master' | 193 buildroot = '/b/cbuild/etc_master' |
| 196 elif config_dict['internal']: | 194 elif config_dict['internal']: |
| 197 buildroot = '/b/cbuild/internal_master' | 195 buildroot = '/b/cbuild/internal_master' |
| 198 else: | 196 else: |
| 199 buildroot = '/b/cbuild/external_master' | 197 buildroot = '/b/cbuild/external_master' |
| 200 props.setProperty('buildroot', buildroot, self._PROPERTY_SOURCE) | 198 props.setProperty('buildroot', buildroot, self._PROPERTY_SOURCE) |
| 201 | 199 |
| 202 props.setProperty('extra_args', options.get('extra_args', []), | |
| 203 self._PROPERTY_SOURCE) | |
| 204 props.setProperty('slaves_request', options.get('slaves_request', []), | 200 props.setProperty('slaves_request', options.get('slaves_request', []), |
| 205 self._PROPERTY_SOURCE) | 201 self._PROPERTY_SOURCE) |
| 206 props.setProperty('chromeos_config', config, self._PROPERTY_SOURCE) | 202 props.setProperty('cbb_config', config, self._PROPERTY_SOURCE) |
| 207 | 203 |
| 208 return props | 204 return props |
| 209 | 205 |
| 210 def create_buildset(self, ssid, parsed_job): | 206 def create_buildset(self, ssid, parsed_job): |
| 211 """Overriding base class method.""" | 207 """Overriding base class method.""" |
| 212 dlist = [] | 208 dlist = [] |
| 213 buildset_name = '%s:%s' % (parsed_job['user'], parsed_job['name']) | 209 buildset_name = '%s:%s' % (parsed_job['user'], parsed_job['name']) |
| 214 for bot in parsed_job['bot']: | 210 for bot in parsed_job['bot']: |
| 215 config = self.cbuildbot_configs.get(bot) | 211 config = self.cbuildbot_configs.get(bot) |
| 216 if config: | 212 if config: |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 244 m.set_type("text/html") | 240 m.set_type("text/html") |
| 245 m['Date'] = formatdate(localtime=True) | 241 m['Date'] = formatdate(localtime=True) |
| 246 m['Subject'] = 'Tryjob failed validation' | 242 m['Subject'] = 'Tryjob failed validation' |
| 247 m['From'] = self.from_addr | 243 m['From'] = self.from_addr |
| 248 m['Reply-To'] = self.reply_to | 244 m['Reply-To'] = self.reply_to |
| 249 result = defer.Deferred() | 245 result = defer.Deferred() |
| 250 sender_factory = SMTPSenderFactory(self.from_addr, emails, | 246 sender_factory = SMTPSenderFactory(self.from_addr, emails, |
| 251 StringIO(m.as_string()), result) | 247 StringIO(m.as_string()), result) |
| 252 reactor.connectTCP(self.smtp_host, 25, sender_factory) | 248 reactor.connectTCP(self.smtp_host, 25, sender_factory) |
| 253 | 249 |
| 254 @defer.deferredGenerator | 250 @defer.inlineCallbacks |
| 255 def gotChange(self, change, important): | 251 def gotChange(self, change, important): |
| 256 """Process the received data and send the queue buildset.""" | 252 """Process the received data and send the queue buildset.""" |
| 257 # Implicitly skips over non-files like directories. | |
| 258 if len(change.files) != 1: | |
| 259 # We only accept changes with 1 diff file. | |
| 260 raise BadJobfile( | |
| 261 'Try job with too many files %s' % (','.join(change.files))) | |
| 262 | |
| 263 # Find poller that this change came from. | 253 # Find poller that this change came from. |
| 264 for poller in self.pollers: | 254 for poller in self.pollers: |
| 265 if poller.repourl == change.repository: | 255 if not isinstance(poller, gitiles_poller.GitilesPoller): |
| 256 continue | |
| 257 if poller.repo_url == change.repository: | |
| 266 break | 258 break |
| 267 else: | 259 else: |
| 268 raise BadJobfile( | 260 raise BadJobfile( |
| 269 'Received tryjob from unsupported repository %s' % change.repository) | 261 'Received tryjob from unsupported repository %s' % change.repository) |
| 270 | 262 |
| 271 # pylint: disable=W0631 | 263 # pylint: disable=W0631 |
| 272 wfd = defer.waitForDeferred( | 264 file_contents = yield self.loadGitilesChangeFile(poller, change) |
| 273 get_file_contents(poller, change.branch, change.files[0])) | |
| 274 yield wfd | |
| 275 | 265 |
| 276 parsed = None | 266 parsed = {} |
| 277 try: | 267 try: |
| 278 parsed = self.load_job(wfd.getResult()) | 268 parsed = self.load_job(file_contents) |
| 279 self.validate_job(parsed) | 269 self.validate_job(parsed) |
| 280 self.updateJobDesc(parsed) | 270 self.updateJobDesc(parsed) |
| 281 except BadJobfile as e: | 271 except BadJobfile as e: |
| 282 self.send_validation_fail_email(parsed.setdefault('name', ''), | 272 self.send_validation_fail_email(parsed.setdefault('name', ''), |
| 283 parsed['email'], str(e)) | 273 parsed['email'], str(e)) |
| 284 raise | 274 raise |
| 285 except Exception as e: | 275 except Exception as e: |
| 286 print 'EXCEPTION:', e | 276 print 'EXCEPTION:', e |
| 287 import traceback | 277 import traceback |
| 288 traceback.print_exc() | 278 traceback.print_exc() |
| 289 raise | 279 raise |
| 290 | 280 |
| 291 # The sourcestamp/buildsets created will be merge-able. | 281 # The sourcestamp/buildsets created will be merge-able. |
| 292 d = self.master.db.sourcestamps.addSourceStamp( | 282 ssid = yield self.master.db.sourcestamps.addSourceStamp( |
| 293 branch=change.branch, | 283 branch=change.branch, |
| 294 revision=change.revision, | 284 revision=change.revision, |
| 295 project=change.project, | 285 project=change.project, |
| 296 repository=change.repository, | 286 repository=change.repository, |
| 297 changeids=[change.number]) | 287 changeids=[change.number]) |
| 298 d.addCallback(self.create_buildset, parsed) | 288 yield self.create_buildset(ssid, parsed) |
| 299 d.addErrback(log.err, "Failed to queue a try job!") | 289 |
| 290 @defer.inlineCallbacks | |
| 291 def loadGitilesChangeFile(self, poller, change): | |
| 292 if len(change.files) != 1: | |
| 293 # We only accept changes with 1 diff file. | |
| 294 raise BadJobfile( | |
|
David James
2015/09/15 19:35:51
What happens when somebody has a commit that affec
phobbs
2015/09/15 21:27:49
I think there would still be only one diff in that
David James
2015/09/15 21:34:37
I mean that somebody did a manual commit in the tr
dnj
2015/09/15 21:55:52
Yes, we expect a push that has been formulated by
| |
| 295 'Try job with too many files %s' % (','.join(change.files))) | |
| 296 | |
| 297 # Load the contents of the modified file. | |
| 298 path = self._GITILES_PATH_TMPL % { | |
| 299 'repo': poller.repo_path, | |
| 300 'revision': change.revision, | |
| 301 'path': change.files[0], | |
| 302 } | |
| 303 contents_b64 = yield poller.agent.request('GET', path, retry=5, | |
| 304 protocol=StringResponse.Get) | |
| 305 defer.returnValue(base64.b64decode(contents_b64)) | |
| OLD | NEW |