| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 |
| OLD | NEW |