Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(641)

Side by Side Diff: appengine/chromium_rietveld/codereview/buildbucket.py

Issue 1344253002: Rietveld: schedule builds on buildbucket (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: remove -dev Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | appengine/chromium_rietveld/codereview/views_chromium.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
32 from codereview import net 33 from codereview import net
33 34
34 EPOCH = datetime.datetime.utcfromtimestamp(0) 35 EPOCH = datetime.datetime.utcfromtimestamp(0)
35 BUILDBUCKET_APP_ID = ( 36 BUILDBUCKET_APP_ID = (
36 'cr-buildbucket-test' if common.IS_DEV else 'cr-buildbucket') 37 'cr-buildbucket-test' if common.IS_DEV else 'cr-buildbucket')
37 BUILDBUCKET_API_ROOT = ( 38 BUILDBUCKET_API_ROOT = (
38 'https://%s.appspot.com/_ah/api/buildbucket/v1' % BUILDBUCKET_APP_ID) 39 'https://%s.appspot.com/_ah/api/buildbucket/v1' % BUILDBUCKET_APP_ID)
39 # See tag conventions http://cr-buildbucket.appspot.com/#docs/conventions 40 # See the convention
41 # https://chromium.googlesource.com/infra/infra/+/master/appengine/cr-buildbucke t/doc/index.md#buildset-tag
40 BUILDSET_TAG_FORMAT = 'patch/rietveld/{hostname}/{issue}/{patch}' 42 BUILDSET_TAG_FORMAT = 'patch/rietveld/{hostname}/{issue}/{patch}'
41 43
42 AUTH_SERVICE_ID = 'chrome-infra-auth' 44 AUTH_SERVICE_ID = 'chrome-infra-auth'
43 IMPERSONATION_TOKEN_MINT_URL = ( 45 IMPERSONATION_TOKEN_MINT_URL = (
44 'https://%s.appspot.com/auth_service/api/v1/delegation/token/create' % 46 'https://%s.appspot.com/auth_service/api/v1/delegation/token/create' %
45 AUTH_SERVICE_ID) 47 AUTH_SERVICE_ID)
46 IMPERSONATION_TOKEN_CACHE_KEY_FORMAT = 'impersonation_token/v1/%s' 48 IMPERSONATION_TOKEN_CACHE_KEY_FORMAT = 'impersonation_token/v1/%s'
47 49
48 50
49 class BuildBucketError(Exception): 51 class BuildBucketError(Exception):
(...skipping 13 matching lines...) Expand all
63 @property 65 @property
64 def is_from_buildbucket(self): 66 def is_from_buildbucket(self):
65 # Used in build_result.html template. 67 # Used in build_result.html template.
66 return True 68 return True
67 69
68 @classmethod 70 @classmethod
69 def convert_status_to_result(cls, build): 71 def convert_status_to_result(cls, build):
70 """Converts build status to TryJobResult.result. 72 """Converts build status to TryJobResult.result.
71 73
72 See buildbucket docs here: 74 See buildbucket docs here:
73 https://cr-buildbucket.appspot.com/#/docs/build 75 https://chromium.googlesource.com/infra/infra/+/master/appengine/cr-buildbuc ket/doc/index.md#Build
74 """ 76 """
75 status = build.get('status') 77 status = build.get('status')
76 if status == 'SCHEDULED': 78 if status == 'SCHEDULED':
77 return cls.TRYPENDING 79 return cls.TRYPENDING
78 80
79 if status == 'COMPLETED': 81 if status == 'COMPLETED':
80 if build.get('result') == 'SUCCESS': 82 if build.get('result') == 'SUCCESS':
81 return cls.SUCCESS 83 return cls.SUCCESS
82 if build.get('result') == 'FAILURE': 84 if build.get('result') == 'FAILURE':
83 if build.get('failure_reason') == 'BUILD_FAILURE': 85 if build.get('failure_reason') == 'BUILD_FAILURE':
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
153 def get_builds_for_patchset_async(project, issue_id, patchset_id): 155 def get_builds_for_patchset_async(project, issue_id, patchset_id):
154 """Queries BuildBucket for builds associated with the patchset. 156 """Queries BuildBucket for builds associated with the patchset.
155 157
156 Requests for max 500 builds and does not check "next_cursor". Currently if 158 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 159 more than 100 builds are requested, only 100 are returned. Presumably there
158 will be no patchsets with >100 builds. 160 will be no patchsets with >100 builds.
159 161
160 Returns: 162 Returns:
161 A list of buildbucket build dicts. 163 A list of buildbucket build dicts.
162 """ 164 """
163 # See tag conventions http://cr-buildbucket.appspot.com/#docs/conventions . 165 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 = { 166 params = {
177 'max_builds': 500, 167 'max_builds': 500,
178 'tag': 'buildset:%s' % buildset_tag, 168 'tag': 'buildset:%s' % buildset_tag,
179 } 169 }
180 170
181 logging.info( 171 logging.info(
182 'Fetching builds for patchset %s/%s. Buildset: %s', 172 'Fetching builds for patchset %s/%s. Buildset: %s',
183 issue_id, patchset_id, buildset_tag) 173 issue_id, patchset_id, buildset_tag)
184 try: 174 try:
185 resp = yield rpc_async('GET', 'search', params=params) 175 resp = yield rpc_async('GET', 'search', params=params)
(...skipping 19 matching lines...) Expand all
205 try_job_result = BuildbucketTryJobResult.from_build(build) 195 try_job_result = BuildbucketTryJobResult.from_build(build)
206 if not try_job_result.builder: 196 if not try_job_result.builder:
207 logging.info( 197 logging.info(
208 'Build %s does not have a builder' % try_job_result.build_id) 198 'Build %s does not have a builder' % try_job_result.build_id)
209 continue 199 continue
210 results.append(try_job_result) 200 results.append(try_job_result)
211 raise ndb.Return(results) 201 raise ndb.Return(results)
212 202
213 203
214 ################################################################################ 204 ################################################################################
205 ## Scheduling builds.
206
207
208 def schedule(issue, patchset_id, builds):
209 """Schedules builds on buildbucket.
210
211 |builds| is a list of (master, builder) tuples, where master must not have
212 '.master' prefix.
213 """
214 account = models.Account.current_user_account
215 assert account, 'User is not logged in; cannot schedule builds.'
216
217 if not builds:
218 return
219
220 self_hostname = common.get_preferred_domain(issue.project)
221
222 req = {'builds':[]}
223 opid = uuid.uuid4()
224
225 for i, (master, builder) in enumerate(builds):
226 # Build definitions are similar to what CQ produces:
227 # https://chrome-internal.googlesource.com/infra/infra_internal/+/c3092da989 75c7a3e083093f21f0f4130c66a51c/commit_queue/buildbucket_util.py#171
228 req['builds'].append({
229 'bucket': 'master.%s' % master,
230 'parameters_json': json.dumps({
231 'builder_name': builder,
232 'changes': [{
233 'author': {'email': issue.owner.email()},
234 'url': 'https://%s/%s/%s/' % (
235 self_hostname, issue.key.id(), patchset_id)
236 }],
237 'properties': {
238 'issue': issue.key.id(),
239 'master': master,
240 'patch_project': issue.project,
241 'patch_storage': 'rietveld',
242 'patchset': patchset_id,
243 'project': issue.project,
244 'rietveld': self_hostname,
245 },
246 }),
247 'tags': [
248 'builder:%s' % builder,
249 'buildset:%s' % get_buildset_for(
250 issue.project, issue.key.id(), patchset_id),
251 'master:%s' % master,
252 'user_agent:rietveld',
253 ],
254 'client_operation_id': '%s:%s' % (opid, i),
255 })
256
257 logging.debug(
258 'Scheduling %d builds on behalf of %s', len(req['builds']), account.email)
259 res = rpc_async('PUT', 'builds/batch', payload=req).get_result()
260 for r in res['results']:
261 error = r.get('error')
262 if error:
263 logging.error('Build scheduling failed. Response: %r', res)
264 raise BuildBucketError('Could not schedule build(s): %r' % error)
265
266 actual_builds = [r['build'] for r in res['results']]
267 logging.info(
268 'Scheduled buildbucket builds: %r',
269 ', '.join([str(b['id']) for b in actual_builds]))
270 return actual_builds
271
272
273 ################################################################################
215 ## Buildbucket RPC. 274 ## Buildbucket RPC.
216 275
217 276
218 @ndb.tasklet 277 @ndb.tasklet
219 def _mint_delegation_token_async(): 278 def _mint_delegation_token_async():
220 """Generates an access token to impersonate the current user, if any. 279 """Generates an access token to impersonate the current user, if any.
221 280
222 Memcaches the token. 281 Memcaches the token.
223 """ 282 """
224 account = models.Account.current_user_account 283 account = models.Account.current_user_account
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
319 return None 378 return None
320 return value 379 return value
321 380
322 381
323 def timestamp_to_datetime(timestamp): 382 def timestamp_to_datetime(timestamp):
324 if timestamp is None: 383 if timestamp is None:
325 return None 384 return None
326 if isinstance(timestamp, basestring): 385 if isinstance(timestamp, basestring):
327 timestamp = int(timestamp) 386 timestamp = int(timestamp)
328 return EPOCH + datetime.timedelta(microseconds=timestamp) 387 return EPOCH + datetime.timedelta(microseconds=timestamp)
388
389
390 def get_buildset_for(project, issue_id, patchset_id):
391 # See the convention
392 # https://chromium.googlesource.com/infra/infra/+/master/appengine/cr-buildbuc ket/doc/index.md#buildset-tag
393 hostname = common.get_preferred_domain(project, default_to_appid=False)
394 if not hostname:
395 logging.error(
396 'Preferred domain name for this app is not set. '
397 'See PREFERRED_DOMAIN_NAMES in settings.py: %r', hostname)
398 raise ndb.Return([])
399
400 return BUILDSET_TAG_FORMAT.format(
401 hostname=hostname,
402 issue=issue_id,
403 patch=patchset_id,
404 )
OLDNEW
« no previous file with comments | « no previous file | appengine/chromium_rietveld/codereview/views_chromium.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698