def retry(self, request): """Retries an existing build.""" lease_expiration_date = parse_datetime(request.lease_expiration_ts) errors.validate_lease_expiration_date(lease_expiration_date) build = model.Build.get_by_id(request.id) if not build: raise errors.BuildNotFoundError('Build %s not found' % request.id) check_scheduling_permissions([build.bucket_id]) # Prepare v2 request. sbr = rpc_pb2.ScheduleBuildRequest( builder=build.proto.builder, request_id=request.client_operation_id, canary=common_pb2.YES if build.canary else common_pb2.NO, properties=build.proto.input.properties, gerrit_changes=build.proto.input.gerrit_changes[:], ) build.tags_to_protos(sbr.tags) if build.input_properties_bytes: # pragma: no branch sbr.properties.ParseFromString(build.input_properties_bytes) if build.proto.input.HasField('gitiles_commit'): # pragma: no branch sbr.gitiles_commit.CopyFrom(build.proto.input.gitiles_commit) # Read PubSub callback. pubsub_callback_auth_token = None if request.pubsub_callback: # pragma: no branch pubsub_callback_auth_token = request.pubsub_callback.auth_token pubsub_callback_to_notification_config(request.pubsub_callback, sbr.notify) with _wrap_validation_error(): validation.validate_notification_config(sbr.notify) # Remove properties from parameters. params = build.parameters.copy() params.pop(model.PROPERTIES_PARAMETER, None) # Create the build. build_req = creation.BuildRequest( schedule_build_request=sbr, parameters=params, lease_expiration_date=lease_expiration_date, retry_of=request.id, pubsub_callback_auth_token=pubsub_callback_auth_token, ) build = creation.add_async(build_req).get_result() return build_to_response_message(build, include_lease_key=True)
def txn(): validate_lease_key(lease_key) if lease_expiration_date is None: raise errors.InvalidInputError( 'Lease expiration date not specified') errors.validate_lease_expiration_date(lease_expiration_date) build = yield model.Build.get_by_id_async(build_id) if build is None: raise errors.BuildNotFoundError() if build.is_ended: msg = '' if (build.result == model.BuildResult.CANCELED and build.cancelation_reason == model.CancelationReason.TIMEOUT): msg = ('Build was marked as timed out ' 'because it did not complete for %s' % model.BUILD_TIMEOUT) raise errors.BuildIsCompletedError(msg) _check_lease(build, lease_key) build.lease_expiration_date = lease_expiration_date yield build.put_async() raise ndb.Return(build)
def lease(build_id, lease_expiration_date=None): """Leases the build, makes it unavailable for the leasing. Changes lease_key to a different value. After the lease expires, a cron task will make the build leasable again. Args: build_id (int): build id. lease_expiration_date (datetime.datetime): lease expiration date. Defaults to 10 seconds from now. Returns: Tuple: success (bool): True if the build was leased build (ndb.Build) """ errors.validate_lease_expiration_date(lease_expiration_date) if lease_expiration_date is None: lease_expiration_date = utils.utcnow() + DEFAULT_LEASE_DURATION @ndb.transactional def try_lease(): build = _get_leasable_build(build_id) if build.proto.status != common_pb2.SCHEDULED or build.is_leased: return False, build build.lease_expiration_date = lease_expiration_date build.regenerate_lease_key() build.leasee = auth.get_current_identity() build.never_leased = False build.put() return True, build updated, build = try_lease() if updated: events.on_build_leased(build) return updated, build
def put_request_message_to_build_request(put_request): """Converts PutRequest to BuildRequest. Raises errors.InvalidInputError if the put_request is invalid. """ lease_expiration_date = parse_datetime(put_request.lease_expiration_ts) errors.validate_lease_expiration_date(lease_expiration_date) # Read parameters. parameters = parse_json_object(put_request.parameters_json, 'parameters_json') parameters = parameters or {} validate_known_build_parameters(parameters) builder = parameters.get(model.BUILDER_PARAMETER) or '' # Validate tags. buildtags.validate_tags(put_request.tags, 'new', builder=builder) # Read properties. Remove them from parameters. props = parameters.pop(model.PROPERTIES_PARAMETER, None) if props is not None and not isinstance(props, dict): raise errors.InvalidInputError( '"properties" parameter must be a JSON object or null') props = props or {} changes = parameters.get(_PARAM_CHANGES) if changes: # pragma: no branch # Buildbucket-Buildbot integration passes repo_url of the first change in # build parameter "changes" as "repository" attribute of SourceStamp. # https://chromium.googlesource.com/chromium/tools/build/+/2c6023d # /scripts/master/buildbucket/changestore.py#140 # Buildbot passes repository of the build source stamp as "repository" # build property. Recipes, in partiular bot_update recipe module, rely on # "repository" property and it is an almost sane property to support in # swarmbucket. repo_url = changes[0].get('repo_url') if repo_url: # pragma: no branch props['repository'] = repo_url # Buildbot-Buildbucket integration converts emails in changes to blamelist # property. emails = [c.get('author', {}).get('email') for c in changes] props['blamelist'] = filter(None, emails) # Create a v2 request. sbr = rpc_pb2.ScheduleBuildRequest( builder=build_pb2.BuilderID(builder=builder), properties=bbutil.dict_to_struct(props), request_id=put_request.client_operation_id, experimental=bbutil.BOOLISH_TO_TRINARY[put_request.experimental], canary=api_common.CANARY_PREFERENCE_TO_TRINARY.get( put_request.canary_preference, common_pb2.UNSET), ) sbr.builder.project, sbr.builder.bucket = config.parse_bucket_id( put_request.bucket) # Parse tags. Extract gitiles commit and gerrit changes. tags, gitiles_commit, gerrit_changes = parse_v1_tags(put_request.tags) sbr.tags.extend(tags) if gitiles_commit: sbr.gitiles_commit.CopyFrom(gitiles_commit) # Gerrit changes explicitly passed via "gerrit_changes" parameter win. gerrit_change_list = parameters.pop('gerrit_changes', None) if gerrit_change_list is not None: if not isinstance(gerrit_change_list, list): # pragma: no cover raise errors.InvalidInputError('gerrit_changes must be a list') try: gerrit_changes = [ json_format.ParseDict(c, common_pb2.GerritChange()) for c in gerrit_change_list ] except json_format.ParseError as ex: # pragma: no cover raise errors.InvalidInputError('Invalid gerrit_changes: %s' % ex) sbr.gerrit_changes.extend(gerrit_changes) if (not gerrit_changes and not sbr.builder.bucket.startswith('master.')): # pragma: no cover changes = parameters.get('changes') if isinstance(changes, list) and changes and not gitiles_commit: legacy_revision = changes[0].get('revision') if legacy_revision: raise errors.InvalidInputError( 'legacy revision without gitiles buildset tag') # Populate Gerrit project from patch_project property. # V2 API users will have to provide this. patch_project = props.get('patch_project') if len(sbr.gerrit_changes) == 1 and isinstance(patch_project, basestring): sbr.gerrit_changes[0].project = patch_project # Read PubSub callback. pubsub_callback_auth_token = None if put_request.pubsub_callback: pubsub_callback_auth_token = put_request.pubsub_callback.auth_token pubsub_callback_to_notification_config(put_request.pubsub_callback, sbr.notify) # Validate the resulting v2 request before continuing. with _wrap_validation_error(): validation.validate_schedule_build_request(sbr, legacy=True) return creation.BuildRequest( schedule_build_request=sbr, parameters=parameters, lease_expiration_date=lease_expiration_date, pubsub_callback_auth_token=pubsub_callback_auth_token, override_builder_cfg=_override_builder_cfg_func(parameters), )
def test_valid(self): dt = utils.utcnow() + datetime.timedelta(minutes=5) errors.validate_lease_expiration_date(dt)
def test_none(self): errors.validate_lease_expiration_date(None)
def test_limit(self): with self.assertRaises(errors.InvalidInputError): dt = utils.utcnow() + datetime.timedelta(days=1) errors.validate_lease_expiration_date(dt)
def test_not_datetime(self): with self.assertRaises(errors.InvalidInputError): errors.validate_lease_expiration_date(1)
def test_past(self): with self.assertRaises(errors.InvalidInputError): yesterday = utils.utcnow() - datetime.timedelta(days=1) errors.validate_lease_expiration_date(yesterday)