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 |