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

Side by Side Diff: appengine/cr-buildbucket/service.py

Issue 1877083003: buildbucket: add retry API. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: retry_of as an attribute Created 4 years, 8 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 | « appengine/cr-buildbucket/model.py ('k') | appengine/cr-buildbucket/test/api_test.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 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 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 datetime 5 import datetime
6 import logging 6 import logging
7 import urlparse 7 import urlparse
8 8
9 from google.appengine.api import taskqueue 9 from google.appengine.api import taskqueue
10 from google.appengine.api import modules 10 from google.appengine.api import modules
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
87 def current_identity_cannot(action_format, *args): 87 def current_identity_cannot(action_format, *args):
88 action = action_format % args 88 action = action_format % args
89 msg = 'User %s cannot %s' % (auth.get_current_identity().to_bytes(), action) 89 msg = 'User %s cannot %s' % (auth.get_current_identity().to_bytes(), action)
90 logging.warning(msg) 90 logging.warning(msg)
91 raise auth.AuthorizationError(msg) 91 raise auth.AuthorizationError(msg)
92 92
93 93
94 @ndb.tasklet 94 @ndb.tasklet
95 def add_async( 95 def add_async(
96 bucket, tags=None, parameters=None, lease_expiration_date=None, 96 bucket, tags=None, parameters=None, lease_expiration_date=None,
97 client_operation_id=None, pubsub_callback=None): 97 client_operation_id=None, pubsub_callback=None, retry_of=None):
98 """Adds the build entity to the build bucket. 98 """Adds the build entity to the build bucket.
99 99
100 Requires the current user to have permissions to add builds to the 100 Requires the current user to have permissions to add builds to the
101 |bucket|. 101 |bucket|.
102 102
103 Args: 103 Args:
104 bucket (str): destination bucket. Required. 104 bucket (str): destination bucket. Required.
105 tags (model.Tags): build tags. 105 tags (model.Tags): build tags.
106 parameters (dict): arbitrary build parameters. Cannot be changed after 106 parameters (dict): arbitrary build parameters. Cannot be changed after
107 build creation. 107 build creation.
108 lease_expiration_date (datetime.datetime): if not None, the build is 108 lease_expiration_date (datetime.datetime): if not None, the build is
109 created as leased and its lease_key is not None. 109 created as leased and its lease_key is not None.
110 pubsub_callback (model.PubsubCallback): callback parameters.
111 client_operation_id (str): client-supplied operation id. If an 110 client_operation_id (str): client-supplied operation id. If an
112 a build with the same client operation id was added during last minute, 111 a build with the same client operation id was added during last minute,
113 it will be returned instead. 112 it will be returned instead.
113 pubsub_callback (model.PubsubCallback): callback parameters.
114 retry_of (int): value for model.Build.retry_of attribute.
114 115
115 Returns: 116 Returns:
116 A new Build. 117 A new Build.
117 """ 118 """
118 if client_operation_id is not None: 119 if client_operation_id is not None:
119 if not isinstance(client_operation_id, basestring): # pragma: no cover 120 if not isinstance(client_operation_id, basestring): # pragma: no cover
120 raise errors.InvalidInputError('client_operation_id must be string') 121 raise errors.InvalidInputError('client_operation_id must be string')
121 if '/' in client_operation_id: # pragma: no cover 122 if '/' in client_operation_id: # pragma: no cover
122 raise errors.InvalidInputError('client_operation_id must not contain /') 123 raise errors.InvalidInputError('client_operation_id must not contain /')
123 validate_bucket_name(bucket) 124 validate_bucket_name(bucket)
(...skipping 20 matching lines...) Expand all
144 145
145 build = model.Build( 146 build = model.Build(
146 id=model.new_build_id(), 147 id=model.new_build_id(),
147 bucket=bucket, 148 bucket=bucket,
148 tags=tags, 149 tags=tags,
149 parameters=parameters, 150 parameters=parameters,
150 status=model.BuildStatus.SCHEDULED, 151 status=model.BuildStatus.SCHEDULED,
151 created_by=identity, 152 created_by=identity,
152 never_leased=lease_expiration_date is None, 153 never_leased=lease_expiration_date is None,
153 pubsub_callback=pubsub_callback, 154 pubsub_callback=pubsub_callback,
155 retry_of=retry_of,
154 ) 156 )
155 if lease_expiration_date is not None: 157 if lease_expiration_date is not None:
156 build.lease_expiration_date = lease_expiration_date 158 build.lease_expiration_date = lease_expiration_date
157 build.leasee = auth.get_current_identity() 159 build.leasee = auth.get_current_identity()
158 build.regenerate_lease_key() 160 build.regenerate_lease_key()
159 161
160 for_swarming = yield swarming.is_for_swarming_async(build) 162 for_swarming = yield swarming.is_for_swarming_async(build)
161 if for_swarming: # pragma: no cover 163 if for_swarming: # pragma: no cover
162 yield swarming.create_task_async(build) 164 yield swarming.create_task_async(build)
163 165
(...skipping 11 matching lines...) Expand all
175 if client_operation_id is not None: 177 if client_operation_id is not None:
176 yield ctx.memcache_set(client_operation_cache_key, build.key.id(), 60) 178 yield ctx.memcache_set(client_operation_cache_key, build.key.id(), 60)
177 raise ndb.Return(build) 179 raise ndb.Return(build)
178 180
179 181
180 def add(*args, **kwargs): 182 def add(*args, **kwargs):
181 """Sync version of add_async.""" 183 """Sync version of add_async."""
182 return add_async(*args, **kwargs).get_result() 184 return add_async(*args, **kwargs).get_result()
183 185
184 186
187 def retry(
188 build_id, lease_expiration_date=None, client_operation_id=None,
189 pubsub_callback=None):
190 """Adds a build with same bucket, parameters and tags as the given one."""
191 build = model.Build.get_by_id(build_id)
192 if not build:
193 raise errors.BuildNotFoundError('Build %s not found' % build_id)
194 return add(
195 build.bucket,
196 tags=build.tags,
197 parameters=build.parameters,
198 lease_expiration_date=lease_expiration_date,
199 client_operation_id=client_operation_id,
200 pubsub_callback=pubsub_callback,
201 retry_of=build_id,
202 )
203
204
185 def get(build_id): 205 def get(build_id):
186 """Gets a build by |build_id|. 206 """Gets a build by |build_id|.
187 207
188 Requires the current user to have permissions to view the build. 208 Requires the current user to have permissions to view the build.
189 """ 209 """
190 build = model.Build.get_by_id(build_id) 210 build = model.Build.get_by_id(build_id)
191 if not build: 211 if not build:
192 return None 212 return None
193 if not acl.can_view_build(build): 213 if not acl.can_view_build(build):
194 raise current_identity_cannot('view build %s', build.key.id()) 214 raise current_identity_cannot('view build %s', build.key.id())
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
233 validate_bucket_name(bucket) 253 validate_bucket_name(bucket)
234 254
235 for bucket in buckets: 255 for bucket in buckets:
236 if not acl.can_search_builds(bucket): 256 if not acl.can_search_builds(bucket):
237 raise current_identity_cannot('search builds in bucket %s', bucket) 257 raise current_identity_cannot('search builds in bucket %s', bucket)
238 258
239 259
240 def search( 260 def search(
241 buckets=None, tags=None, 261 buckets=None, tags=None,
242 status=None, result=None, failure_reason=None, cancelation_reason=None, 262 status=None, result=None, failure_reason=None, cancelation_reason=None,
243 created_by=None, max_builds=None, start_cursor=None): 263 created_by=None, max_builds=None, start_cursor=None,
264 retry_of=None):
244 """Searches for builds. 265 """Searches for builds.
245 266
246 Args: 267 Args:
247 buckets (list of str): a list of buckets to search in. 268 buckets (list of str): a list of buckets to search in.
248 A build must be in one of the buckets. 269 A build must be in one of the buckets.
249 tags (list of str): a list of tags that a build must have. 270 tags (list of str): a list of tags that a build must have.
250 All of the |tags| must be present in a build. 271 All of the |tags| must be present in a build.
251 status (model.BuildStatus): build status. 272 status (model.BuildStatus): build status.
252 result (model.BuildResult): build result. 273 result (model.BuildResult): build result.
253 failure_reason (model.FailureReason): failure reason. 274 failure_reason (model.FailureReason): failure reason.
254 cancelation_reason (model.CancelationReason): build cancelation reason. 275 cancelation_reason (model.CancelationReason): build cancelation reason.
255 created_by (str): identity who created a build. 276 created_by (str): identity who created a build.
256 max_builds (int): maximum number of builds to return. 277 max_builds (int): maximum number of builds to return.
257 start_cursor (string): a value of "next" cursor returned by previous 278 start_cursor (string): a value of "next" cursor returned by previous
258 search_by_tags call. If not None, return next builds in the query. 279 search_by_tags call. If not None, return next builds in the query.
280 retry_of (int): value of retry_of attribute.
259 281
260 Returns: 282 Returns:
261 A tuple: 283 A tuple:
262 builds (list of Build): query result. 284 builds (list of Build): query result.
263 next_cursor (string): cursor for the next page. 285 next_cursor (string): cursor for the next page.
264 None if there are no more builds. 286 None if there are no more builds.
265 """ 287 """
266 if buckets is not None and not isinstance(buckets, list): 288 if buckets is not None and not isinstance(buckets, list):
267 raise errors.InvalidInputError('Buckets must be a list or None') 289 raise errors.InvalidInputError('Buckets must be a list or None')
268 validate_tags(tags) 290 validate_tags(tags)
269 tags = tags or [] 291 tags = tags or []
270 max_builds = fix_max_builds(max_builds) 292 max_builds = fix_max_builds(max_builds)
271 created_by = parse_identity(created_by) 293 created_by = parse_identity(created_by)
272 294
273 if buckets: 295 if buckets:
274 _check_search_acls(buckets) 296 _check_search_acls(buckets)
297 elif retry_of:
298 retry_of_build = model.Build.get_by_id(retry_of)
299 if retry_of_build:
300 buckets = [retry_of_build.bucket]
275 else: 301 else:
276 buckets = acl.get_available_buckets() 302 buckets = acl.get_available_buckets()
277 if buckets is not None and len(buckets) == 0: 303 if buckets is not None and len(buckets) == 0:
278 return [], None 304 return [], None
279 if buckets: 305 if buckets:
280 buckets = set(buckets) 306 buckets = set(buckets)
281 assert buckets is None or buckets 307 assert buckets is None or buckets
282 308
283 check_buckets_locally = False 309 check_buckets_locally = retry_of is not None
284 q = model.Build.query() 310 q = model.Build.query()
285 for t in tags: 311 for t in tags:
286 if t.startswith('buildset:'): 312 if t.startswith('buildset:'):
287 check_buckets_locally = True 313 check_buckets_locally = True
288 q = q.filter(model.Build.tags == t) 314 q = q.filter(model.Build.tags == t)
289 filter_if = lambda p, v: q if v is None else q.filter(p == v) 315 filter_if = lambda p, v: q if v is None else q.filter(p == v)
290 q = filter_if(model.Build.status, status) 316 q = filter_if(model.Build.status, status)
291 q = filter_if(model.Build.result, result) 317 q = filter_if(model.Build.result, result)
292 q = filter_if(model.Build.failure_reason, failure_reason) 318 q = filter_if(model.Build.failure_reason, failure_reason)
293 q = filter_if(model.Build.cancelation_reason, cancelation_reason) 319 q = filter_if(model.Build.cancelation_reason, cancelation_reason)
294 q = filter_if(model.Build.created_by, created_by) 320 q = filter_if(model.Build.created_by, created_by)
321 q = filter_if(model.Build.retry_of, retry_of)
295 # buckets is None if the current identity has access to ALL buckets. 322 # buckets is None if the current identity has access to ALL buckets.
296 if buckets and not check_buckets_locally: 323 if buckets and not check_buckets_locally:
297 q = q.filter(model.Build.bucket.IN(buckets)) 324 q = q.filter(model.Build.bucket.IN(buckets))
298 q = q.order(model.Build.key) 325 q = q.order(model.Build.key)
299 326
300 local_predicate = None 327 local_predicate = None
301 328
302 def local_status_and_bucket_check(build): 329 def local_status_and_bucket_check(build):
303 if status is not None and build.status != status: # pragma: no coverage 330 if status is not None and build.status != status: # pragma: no coverage
304 return False 331 return False
(...skipping 472 matching lines...) Expand 10 before | Expand all | Expand 10 after
777 if isinstance(identity, basestring): 804 if isinstance(identity, basestring):
778 if not identity: # pragma: no cover 805 if not identity: # pragma: no cover
779 return None 806 return None
780 if ':' not in identity: # pragma: no branch 807 if ':' not in identity: # pragma: no branch
781 identity = 'user:%s' % identity 808 identity = 'user:%s' % identity
782 try: 809 try:
783 identity = auth.Identity.from_bytes(identity) 810 identity = auth.Identity.from_bytes(identity)
784 except ValueError as ex: 811 except ValueError as ex:
785 raise errors.InvalidInputError('Invalid identity identity: %s' % ex) 812 raise errors.InvalidInputError('Invalid identity identity: %s' % ex)
786 return identity 813 return identity
OLDNEW
« no previous file with comments | « appengine/cr-buildbucket/model.py ('k') | appengine/cr-buildbucket/test/api_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698