Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2008 Google Inc. | 1 # Copyright 2008 Google Inc. |
| 2 # | 2 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. | 4 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at | 5 # You may obtain a copy of the License at |
| 6 # | 6 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 7 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 8 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 9 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 10 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and | 12 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. | 13 # limitations under the License. |
| 14 | 14 |
| 15 """Rietveld-BuildBucket integration module.""" | 15 """Rietveld-BuildBucket integration module.""" |
| 16 | 16 |
| 17 import datetime | 17 import datetime |
| 18 import json | 18 import json |
| 19 import logging | 19 import logging |
| 20 import os | 20 import os |
| 21 import urllib | 21 import urllib |
| 22 import uuid | |
| 22 | 23 |
| 23 from google.appengine.api import app_identity | 24 from google.appengine.api import app_identity |
| 24 from google.appengine.api import memcache | 25 from google.appengine.api import memcache |
| 25 from google.appengine.api import users | 26 from google.appengine.api import users |
| 26 from google.appengine.ext import ndb | 27 from google.appengine.ext import ndb |
| 27 | 28 |
| 28 from django.conf import settings | 29 from django.conf import settings |
| 29 | 30 |
| 30 from codereview import common | 31 from codereview import common |
| 31 from codereview import models | 32 from codereview import models |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 153 def get_builds_for_patchset_async(project, issue_id, patchset_id): | 154 def get_builds_for_patchset_async(project, issue_id, patchset_id): |
| 154 """Queries BuildBucket for builds associated with the patchset. | 155 """Queries BuildBucket for builds associated with the patchset. |
| 155 | 156 |
| 156 Requests for max 500 builds and does not check "next_cursor". Currently if | 157 Requests for max 500 builds and does not check "next_cursor". Currently if |
| 157 more than 100 builds are requested, only 100 are returned. Presumably there | 158 more than 100 builds are requested, only 100 are returned. Presumably there |
| 158 will be no patchsets with >100 builds. | 159 will be no patchsets with >100 builds. |
| 159 | 160 |
| 160 Returns: | 161 Returns: |
| 161 A list of buildbucket build dicts. | 162 A list of buildbucket build dicts. |
| 162 """ | 163 """ |
| 163 # See tag conventions http://cr-buildbucket.appspot.com/#docs/conventions . | 164 buildset_tag = get_buildset_for(project, issue_id, patchset_id) |
| 164 hostname = common.get_preferred_domain(project, default_to_appid=False) | |
| 165 if not hostname: | |
| 166 logging.error( | |
| 167 'Preferred domain name for this app is not set. ' | |
| 168 'See PREFERRED_DOMAIN_NAMES in settings.py: %r', hostname) | |
| 169 raise ndb.Return([]) | |
| 170 | |
| 171 buildset_tag = BUILDSET_TAG_FORMAT.format( | |
| 172 hostname=hostname, | |
| 173 issue=issue_id, | |
| 174 patch=patchset_id, | |
| 175 ) | |
| 176 params = { | 165 params = { |
| 177 'max_builds': 500, | 166 'max_builds': 500, |
| 178 'tag': 'buildset:%s' % buildset_tag, | 167 'tag': 'buildset:%s' % buildset_tag, |
| 179 } | 168 } |
| 180 | 169 |
| 181 logging.info( | 170 logging.info( |
| 182 'Fetching builds for patchset %s/%s. Buildset: %s', | 171 'Fetching builds for patchset %s/%s. Buildset: %s', |
| 183 issue_id, patchset_id, buildset_tag) | 172 issue_id, patchset_id, buildset_tag) |
| 184 try: | 173 try: |
| 185 resp = yield rpc_async('GET', 'search', params=params) | 174 resp = yield rpc_async('GET', 'search', params=params) |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 205 try_job_result = BuildbucketTryJobResult.from_build(build) | 194 try_job_result = BuildbucketTryJobResult.from_build(build) |
| 206 if not try_job_result.builder: | 195 if not try_job_result.builder: |
| 207 logging.info( | 196 logging.info( |
| 208 'Build %s does not have a builder' % try_job_result.build_id) | 197 'Build %s does not have a builder' % try_job_result.build_id) |
| 209 continue | 198 continue |
| 210 results.append(try_job_result) | 199 results.append(try_job_result) |
| 211 raise ndb.Return(results) | 200 raise ndb.Return(results) |
| 212 | 201 |
| 213 | 202 |
| 214 ################################################################################ | 203 ################################################################################ |
| 204 ## Scheduling builds. | |
| 205 | |
| 206 | |
| 207 def schedule(issue, patchset_id, builds): | |
| 208 """Schedules builds on buildbucket. | |
| 209 | |
| 210 |builds| is a list of (master, builder) tuples, where master must not have | |
| 211 '.master' prefix. | |
| 212 """ | |
| 213 account = models.Account.current_user_account | |
| 214 assert account, 'User is not logged in; cannot schedule builds.' | |
| 215 | |
| 216 if not builds: | |
| 217 return | |
| 218 | |
| 219 self_hostname = common.get_preferred_domain(issue.project) | |
| 220 | |
| 221 req = {'builds':[]} | |
| 222 opid = uuid.uuid4() | |
| 223 | |
| 224 for i, (master, builder) in enumerate(builds): | |
| 225 # Build definitions are similar to what CQ produces: | |
| 226 # https://chrome-internal.googlesource.com/infra/infra_internal/+/c3092da989 75c7a3e083093f21f0f4130c66a51c/commit_queue/buildbucket_util.py#171 | |
| 227 req['builds'].append({ | |
| 228 'bucket': 'master.%s' % master, | |
| 229 'parameters_json': json.dumps({ | |
| 230 'builder_name': builder, | |
| 231 'changes': [{ | |
| 232 'author': {'email': issue.owner.email()}, | |
| 233 'url': 'https://%s/%s/%s/' % ( | |
| 234 self_hostname, issue.key.id(), patchset_id) | |
| 235 }], | |
| 236 'properties': { | |
| 237 'issue': issue.key.id(), | |
| 238 'master': master, | |
| 239 'patch_project': issue.project, | |
| 240 'patch_storage': 'rietveld', | |
| 241 'patchset': patchset_id, | |
| 242 'project': issue.project, | |
| 243 'rietveld': self_hostname, | |
| 244 }, | |
| 245 }), | |
| 246 'tags': [ | |
| 247 'builder:%s' % builder, | |
| 248 'buildset:%s' % get_buildset_for( | |
| 249 issue.project, issue.key.id(), patchset_id), | |
| 250 'master:%s' % master, | |
| 251 'user_agent:rietveld', | |
| 252 ], | |
| 253 'client_operation_id': '%s:%s' % (opid, i), | |
| 254 }) | |
| 255 | |
| 256 logging.debug( | |
| 257 'Scheduling %d builds on behalf of %s', len(req['builds']), account.email) | |
| 258 res = rpc_async('PUT', 'builds/batch', payload=req).get_result() | |
| 259 for r in res['results']: | |
| 260 error = r.get('error') | |
| 261 if error: | |
| 262 logging.error('Build scheduling failed. Response: %r', res) | |
| 263 raise BuildBucketError('Could not schedule build(s): %r' % error) | |
|
Vadim Sh.
2015/09/16 21:57:38
how is this displayed to the user?
nodir
2015/09/17 17:44:17
very rough: on any 500 the old UI shows an alert t
| |
| 264 | |
| 265 actual_builds = [r['build'] for r in res['results']] | |
| 266 logging.info( | |
| 267 'Scheduled buildbucket builds: %r', | |
| 268 ', '.join([str(b['id']) for b in actual_builds])) | |
| 269 return actual_builds | |
| 270 | |
| 271 | |
| 272 ################################################################################ | |
| 215 ## Buildbucket RPC. | 273 ## Buildbucket RPC. |
| 216 | 274 |
| 217 | 275 |
| 218 @ndb.tasklet | 276 @ndb.tasklet |
| 219 def _mint_delegation_token_async(): | 277 def _mint_delegation_token_async(): |
| 220 """Generates an access token to impersonate the current user, if any. | 278 """Generates an access token to impersonate the current user, if any. |
| 221 | 279 |
| 222 Memcaches the token. | 280 Memcaches the token. |
| 223 """ | 281 """ |
| 224 account = models.Account.current_user_account | 282 account = models.Account.current_user_account |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 319 return None | 377 return None |
| 320 return value | 378 return value |
| 321 | 379 |
| 322 | 380 |
| 323 def timestamp_to_datetime(timestamp): | 381 def timestamp_to_datetime(timestamp): |
| 324 if timestamp is None: | 382 if timestamp is None: |
| 325 return None | 383 return None |
| 326 if isinstance(timestamp, basestring): | 384 if isinstance(timestamp, basestring): |
| 327 timestamp = int(timestamp) | 385 timestamp = int(timestamp) |
| 328 return EPOCH + datetime.timedelta(microseconds=timestamp) | 386 return EPOCH + datetime.timedelta(microseconds=timestamp) |
| 387 | |
| 388 | |
| 389 def get_buildset_for(project, issue_id, patchset_id): | |
| 390 # See tag conventions http://cr-buildbucket.appspot.com/#docs/conventions | |
|
Vadim Sh.
2015/09/16 21:57:38
https://
nodir
2015/09/17 17:44:17
Updated all doc links to point to the newer MD doc
| |
| 391 hostname = common.get_preferred_domain(project, default_to_appid=False) | |
| 392 if not hostname: | |
| 393 logging.error( | |
| 394 'Preferred domain name for this app is not set. ' | |
| 395 'See PREFERRED_DOMAIN_NAMES in settings.py: %r', hostname) | |
| 396 raise ndb.Return([]) | |
| 397 | |
| 398 return BUILDSET_TAG_FORMAT.format( | |
| 399 hostname=hostname, | |
| 400 issue=issue_id, | |
| 401 patch=patchset_id, | |
| 402 ) | |
| OLD | NEW |