Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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),
    )
Ejemplo n.º 5
0
 def test_valid(self):
     dt = utils.utcnow() + datetime.timedelta(minutes=5)
     errors.validate_lease_expiration_date(dt)
Ejemplo n.º 6
0
 def test_none(self):
     errors.validate_lease_expiration_date(None)
Ejemplo n.º 7
0
 def test_limit(self):
     with self.assertRaises(errors.InvalidInputError):
         dt = utils.utcnow() + datetime.timedelta(days=1)
         errors.validate_lease_expiration_date(dt)
Ejemplo n.º 8
0
 def test_not_datetime(self):
     with self.assertRaises(errors.InvalidInputError):
         errors.validate_lease_expiration_date(1)
Ejemplo n.º 9
0
 def test_past(self):
     with self.assertRaises(errors.InvalidInputError):
         yesterday = utils.utcnow() - datetime.timedelta(days=1)
         errors.validate_lease_expiration_date(yesterday)