| 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 """Subclasses of various slave command classes.""" | 5 """Subclasses of various slave command classes.""" |
| 6 | 6 |
| 7 from datetime import datetime | 7 from datetime import datetime |
| 8 import copy | 8 import copy |
| 9 import errno | 9 import errno |
| 10 import json | 10 import json |
| 11 import logging | 11 import logging |
| 12 import os | 12 import os |
| 13 import time | 13 import time |
| 14 | 14 |
| 15 from twisted.internet import defer | 15 from twisted.internet import defer |
| 16 from twisted.python import log | 16 from twisted.python import log |
| 17 | 17 |
| 18 from buildbot import interfaces, util | 18 from buildbot import interfaces, util |
| 19 from buildbot.changes.changes import Change | 19 from buildbot.changes.changes import Change |
| 20 from buildbot.process import buildstep | 20 from buildbot.process import buildstep |
| 21 from buildbot.process.properties import WithProperties | 21 from buildbot.process.properties import WithProperties |
| 22 from buildbot.status import builder | 22 from buildbot.status import builder |
| 23 from buildbot.steps import shell | 23 from buildbot.steps import shell |
| 24 from buildbot.steps import source | 24 from buildbot.steps import source |
| 25 import sqlalchemy as sa | 25 import sqlalchemy as sa |
| 26 | 26 |
| 27 from common import annotator | 27 from common import annotator |
| 28 from master import buildbucket |
| 28 from common import chromium_utils | 29 from common import chromium_utils |
| 29 import config | 30 import config |
| 30 | 31 |
| 31 | 32 |
| 32 def change_to_revision(c): | 33 def change_to_revision(c): |
| 33 """Handle revision == None or any invalid value.""" | 34 """Handle revision == None or any invalid value.""" |
| 34 try: | 35 try: |
| 35 return int(str(c.revision).split('@')[-1]) | 36 return int(str(c.revision).split('@')[-1]) |
| 36 except (ValueError, TypeError): | 37 except (ValueError, TypeError): |
| 37 return 0 | 38 return 0 |
| (...skipping 595 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 633 self.annotate_status = builder.SUCCESS | 634 self.annotate_status = builder.SUCCESS |
| 634 self.halt_on_failure = False | 635 self.halt_on_failure = False |
| 635 self.honor_zero_return_code = False | 636 self.honor_zero_return_code = False |
| 636 self.cursor = None | 637 self.cursor = None |
| 637 | 638 |
| 638 self.show_perf = show_perf | 639 self.show_perf = show_perf |
| 639 self.perf_id = perf_id | 640 self.perf_id = perf_id |
| 640 self.perf_report_url_suffix = perf_report_url_suffix | 641 self.perf_report_url_suffix = perf_report_url_suffix |
| 641 self.target = target | 642 self.target = target |
| 642 self.active_master = active_master | 643 self.active_master = active_master |
| 644 self.bb_triggering_service = None |
| 643 | 645 |
| 644 def initialSection(self): | 646 def initialSection(self): |
| 645 """Initializes the annotator's sections. | 647 """Initializes the annotator's sections. |
| 646 | 648 |
| 647 Annotator uses a list of dictionaries which hold information stuch as status | 649 Annotator uses a list of dictionaries which hold information stuch as status |
| 648 and logs for each added step. This method populates the section list with an | 650 and logs for each added step. This method populates the section list with an |
| 649 entry referencing the original buildbot step.""" | 651 entry referencing the original buildbot step.""" |
| 650 if self.sections: | 652 if self.sections: |
| 651 return | 653 return |
| 652 # Add a log section for output before the first section heading. | 654 # Add a log section for output before the first section heading. |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 781 section['closed'] = True | 783 section['closed'] = True |
| 782 if section['step'].isFinished(): | 784 if section['step'].isFinished(): |
| 783 return | 785 return |
| 784 | 786 |
| 785 # Everything was fine on the slave side, | 787 # Everything was fine on the slave side, |
| 786 # so the final result depends on async operations. | 788 # so the final result depends on async operations. |
| 787 async_ops = section['async_ops'] | 789 async_ops = section['async_ops'] |
| 788 if async_ops: | 790 if async_ops: |
| 789 op_list = '\n'.join('* %s' % o['description'] | 791 op_list = '\n'.join('* %s' % o['description'] |
| 790 for o in async_ops) | 792 for o in async_ops) |
| 791 msg = 'Will wait till async operations complete:\n%s' % (op_list,) | 793 msg = 'Will wait till async operations complete:\n%s\n' % (op_list,) |
| 792 section['log'].addStdout(msg) | 794 section['log'].addStdout(msg) |
| 793 | 795 |
| 794 d = defer.DeferredList([o['deferred'] for o in async_ops]) | 796 d = defer.DeferredList([o['deferred'] for o in async_ops]) |
| 795 def finish(results): | 797 def finish(results): |
| 796 try: | 798 try: |
| 797 reasons = [] | 799 reasons = [] |
| 798 status = section['status'] | 800 status = section['status'] |
| 799 for succeeded, defer_result in results: | 801 for succeeded, defer_result in results: |
| 800 if succeeded: | 802 if succeeded: |
| 801 # callback was called. | 803 # callback was called. |
| (...skipping 369 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1171 files = change.get('files') | 1173 files = change.get('files') |
| 1172 if files: | 1174 if files: |
| 1173 change['files'] = sorted(files) | 1175 change['files'] = sorted(files) |
| 1174 | 1176 |
| 1175 return change | 1177 return change |
| 1176 | 1178 |
| 1177 def STEP_TRIGGER(self, spec): | 1179 def STEP_TRIGGER(self, spec): |
| 1178 # Support: @@@STEP_TRIGGER <json spec>@@@ (trigger build(s)). | 1180 # Support: @@@STEP_TRIGGER <json spec>@@@ (trigger build(s)). |
| 1179 try: | 1181 try: |
| 1180 spec = json.loads(spec) | 1182 spec = json.loads(spec) |
| 1183 bucket = spec.get('bucket') |
| 1181 builder_names = spec.get('builderNames') | 1184 builder_names = spec.get('builderNames') |
| 1182 | 1185 properties = spec.get('properties') or {} |
| 1183 changes = spec.get('changes') | 1186 changes = spec.get('changes') |
| 1184 if changes: | 1187 if changes: |
| 1185 assert isinstance(changes, list) | 1188 assert isinstance(changes, list) |
| 1186 changes = map(self.normalizeChangeSpec, changes) | 1189 changes = map(self.normalizeChangeSpec, changes) |
| 1187 | 1190 |
| 1188 if not builder_names: | 1191 if not builder_names: |
| 1189 raise ValueError('builderNames is not specified: %r' % (spec,)) | 1192 raise ValueError('builderNames is not specified: %r' % (spec,)) |
| 1190 | 1193 |
| 1191 # Start builds. | 1194 build = self.command.build |
| 1192 d = self.triggerBuilds(builder_names, spec.get('properties') or {}, | 1195 build_is_from_buildbucket = bool( |
| 1193 changes) | 1196 build.getProperties().getProperty(buildbucket.common.INFO_PROPERTY)) |
| 1197 trigger_via_buildbucket = bucket or build_is_from_buildbucket |
| 1198 |
| 1199 if trigger_via_buildbucket: |
| 1200 d = self.triggerBuildsViaBuildBucket( |
| 1201 bucket, builder_names, properties, changes) |
| 1202 else: |
| 1203 d = self.triggerBuildsAsBuildsets(builder_names, properties, changes) |
| 1194 # addAsyncOpToCursor expects a deferred to return a build result. If a | 1204 # addAsyncOpToCursor expects a deferred to return a build result. If a |
| 1195 # buildset is added, then it is a success. This lambda function returns a | 1205 # buildset is added, then it is a success. This lambda function returns a |
| 1196 # tuple, which is received by addAsyncOpToCursor. | 1206 # tuple, which is received by addAsyncOpToCursor. |
| 1197 d.addCallback(lambda _: (builder.SUCCESS, None)) | 1207 d.addCallback(lambda _: (builder.SUCCESS, None)) |
| 1198 description = 'Triggering build(s) on %s' % (', '.join(builder_names),) | 1208 description = 'Triggering build(s) on %s' % (', '.join(builder_names),) |
| 1199 self.addAsyncOpToCursor(d, description) | 1209 self.addAsyncOpToCursor(d, description) |
| 1200 except Exception as ex: | 1210 except Exception as ex: |
| 1201 self.finishStep(self.cursor, builder.FAILURE, ex) | 1211 self.finishStep(self.cursor, builder.FAILURE, ex) |
| 1202 | 1212 |
| 1203 @staticmethod | 1213 @staticmethod |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1216 """Inserts a new SourceStamp. | 1226 """Inserts a new SourceStamp. |
| 1217 | 1227 |
| 1218 For each change in changes_spec, finds an existing or creates a new Change | 1228 For each change in changes_spec, finds an existing or creates a new Change |
| 1219 object. Then creates a SourceStamp with these changes. | 1229 object. Then creates a SourceStamp with these changes. |
| 1220 | 1230 |
| 1221 Args: | 1231 Args: |
| 1222 master: an instance of buildbot.master.BuildMaster. | 1232 master: an instance of buildbot.master.BuildMaster. |
| 1223 changes_spec (list of dict): a list of change dicts, where each contains | 1233 changes_spec (list of dict): a list of change dicts, where each contains |
| 1224 keyword arguments for | 1234 keyword arguments for |
| 1225 buildbot.db.changes.ChangesConnectorComponent.addChange() function, | 1235 buildbot.db.changes.ChangesConnectorComponent.addChange() function, |
| 1226 except when_timestamp is int instead of datetime. The first change | 1236 except when_timestamp is int (seconds since Unix Epoch) instead of |
| 1227 is used to populate source stamp properties. | 1237 datetime. The first change is used to populate source stamp properties. |
| 1228 """ | 1238 """ |
| 1229 def find_changes_by_revision(revision): | 1239 def find_changes_by_revision(revision): |
| 1230 """Searches for Changes in db by |revision| and returns change ids.""" | 1240 """Searches for Changes in db by |revision| and returns change ids.""" |
| 1231 def find(conn): | 1241 def find(conn): |
| 1232 table = master.db.model.changes | 1242 table = master.db.model.changes |
| 1233 q = sa.select([table.c.changeid]).where(table.c.revision == revision) | 1243 q = sa.select([table.c.changeid]).where(table.c.revision == revision) |
| 1234 return [c.changeid for c in conn.execute(q)] | 1244 return [c.changeid for c in conn.execute(q)] |
| 1235 return master.db.pool.do(find) | 1245 return master.db.pool.do(find) |
| 1236 | 1246 |
| 1237 @defer.inlineCallbacks | 1247 @defer.inlineCallbacks |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1288 ssid = yield master.db.sourcestamps.addSourceStamp( | 1298 ssid = yield master.db.sourcestamps.addSourceStamp( |
| 1289 branch=main_change.branch, | 1299 branch=main_change.branch, |
| 1290 revision=main_change.revision, | 1300 revision=main_change.revision, |
| 1291 repository=main_change.repository, | 1301 repository=main_change.repository, |
| 1292 project=main_change.project, | 1302 project=main_change.project, |
| 1293 changeids=[c.number for c in changes], | 1303 changeids=[c.number for c in changes], |
| 1294 ) | 1304 ) |
| 1295 defer.returnValue(ssid) | 1305 defer.returnValue(ssid) |
| 1296 | 1306 |
| 1297 @defer.inlineCallbacks | 1307 @defer.inlineCallbacks |
| 1298 def triggerBuilds(self, builder_names, properties, changes=None): | 1308 def triggerBuildsViaBuildBucket( |
| 1309 self, bucket_name, builder_names, properties, changes_spec=None): |
| 1310 """Schedules builds on buildbucket.""" |
| 1311 if self.active_master is None: |
| 1312 raise buildbucket.Error( |
| 1313 'In order to trigger builds through buildbucket, ' |
| 1314 'ActiveMaster must be passed to AnnotatorFactory') |
| 1315 build = self.command.build |
| 1316 section = self.cursor |
| 1317 if not self.bb_triggering_service: |
| 1318 self.bb_triggering_service = yield ( |
| 1319 buildbucket.trigger.get_triggering_service(self.active_master)) |
| 1320 changes = map( |
| 1321 buildbucket.trigger.change_from_change_spec, changes_spec or []) |
| 1322 for builder_name in builder_names: |
| 1323 response = yield self.bb_triggering_service.trigger( |
| 1324 build, bucket_name, builder_name, properties, changes) |
| 1325 section['log'].addStdout( |
| 1326 'BuildBucket.put API response: %s' % json.dumps(response, indent=4)) |
| 1327 |
| 1328 @defer.inlineCallbacks |
| 1329 def triggerBuildsAsBuildsets( |
| 1330 self, builder_names, properties, changes_spec=None): |
| 1299 """Creates a new buildset.""" | 1331 """Creates a new buildset.""" |
| 1300 build = self.command.build | 1332 build = self.command.build |
| 1301 master = build.builder.botmaster.parent | 1333 master = build.builder.botmaster.parent |
| 1302 current_properties = build.getProperties() | 1334 current_properties = build.getProperties() |
| 1303 | 1335 |
| 1304 if changes: | 1336 if changes_spec: |
| 1305 # Changes have been specified explicitly. | 1337 # Changes have been specified explicitly. |
| 1306 ssid = yield self.insertSourceStamp(master, changes) | 1338 ssid = yield self.insertSourceStamp(master, changes_spec) |
| 1307 else: | 1339 else: |
| 1308 # Use the same source stamp. | 1340 # Use the same source stamp. |
| 1309 source_stamp = build.getSourceStamp() | 1341 source_stamp = build.getSourceStamp() |
| 1310 if source_stamp.revision is None: | 1342 if source_stamp.revision is None: |
| 1311 revision = current_properties.getProperty('got_revision') | 1343 revision = current_properties.getProperty('got_revision') |
| 1312 if revision: | 1344 if revision: |
| 1313 # The source stamp is relative, but we have the "got_revision", so | 1345 # The source stamp is relative, but we have the "got_revision", so |
| 1314 # make it absolute. | 1346 # make it absolute. |
| 1315 source_stamp = source_stamp.getAbsoluteSourceStamp(revision) | 1347 source_stamp = source_stamp.getAbsoluteSourceStamp(revision) |
| 1316 else: | 1348 else: |
| (...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1468 and starts to wait for remaining steps to finish. Returns command_result | 1500 and starts to wait for remaining steps to finish. Returns command_result |
| 1469 as Deferred.""" | 1501 as Deferred.""" |
| 1470 self.scriptComplete(command_result) | 1502 self.scriptComplete(command_result) |
| 1471 steps_d = self.script_observer.waitForSteps() | 1503 steps_d = self.script_observer.waitForSteps() |
| 1472 # Ignore the waitForSteps' result and return the original result, | 1504 # Ignore the waitForSteps' result and return the original result, |
| 1473 # so the caller of runCommand receives command_result. | 1505 # so the caller of runCommand receives command_result. |
| 1474 steps_d.addCallback(lambda *_: command_result) | 1506 steps_d.addCallback(lambda *_: command_result) |
| 1475 return steps_d | 1507 return steps_d |
| 1476 d.addCallback(onCommandFinished) | 1508 d.addCallback(onCommandFinished) |
| 1477 return d | 1509 return d |
| OLD | NEW |