Пример #1
0
def _sync_build_with_task_result(build_id, task_result):
    """Syncs Build entity in the datastore with a result of the swarming task."""
    @ndb.transactional
    def txn():
        bundle = model.BuildBundle.get(build_id, infra=True)
        if not bundle:  # pragma: no cover
            return None
        build = bundle.build
        status_changed = _sync_build_with_task_result_in_memory(
            build, bundle.infra, task_result)
        if not status_changed:
            return None

        futures = [bundle.put_async()]

        if build.proto.status == common_pb2.STARTED:
            futures.append(events.on_build_starting_async(build))
        elif build.is_ended:  # pragma: no branch
            futures.append(
                model.BuildSteps.cancel_incomplete_steps_async(
                    build_id, build.proto.end_time))
            futures.append(events.on_build_completing_async(build))

        for f in futures:
            f.check_success()
        return build

    build = txn()
    if build:
        if build.proto.status == common_pb2.STARTED:
            events.on_build_started(build)
        elif build.is_ended:  # pragma: no branch
            events.on_build_completed(build)
Пример #2
0
def _sync_build_async(build_id, task_result):
    """Syncs Build entity in the datastore with the swarming task."""
    @ndb.transactional_tasklet
    def txn_async():
        build = yield model.Build.get_by_id_async(build_id)
        if not build:  # pragma: no cover
            raise ndb.Return(None)
        made_change = _sync_build_in_memory(build, task_result)
        if not made_change:
            raise ndb.Return(None)

        futures = [build.put_async()]

        if build.proto.status == common_pb2.STARTED:
            futures.append(events.on_build_starting_async(build))
        elif build.is_ended:  # pragma: no branch
            futures.append(
                model.BuildSteps.cancel_incomplete_steps_async(
                    build_id, build.proto.end_time))
            futures.append(events.on_build_completing_async(build))

        yield futures
        raise ndb.Return(build)

    build = yield txn_async()
    if build:
        if build.proto.status == common_pb2.STARTED:
            events.on_build_started(build)
        elif build.is_ended:  # pragma: no branch
            events.on_build_completed(build)
Пример #3
0
def _complete(build_id,
              lease_key,
              status,
              result_details,
              url=None,
              new_tags=None):
    """Marks a build as completed. Used by succeed and fail methods."""
    validate_lease_key(lease_key)
    validate_url(url)
    buildtags.validate_tags(new_tags, 'append')
    assert model.is_terminal_status(status), status

    @ndb.transactional
    def txn():
        build = _get_leasable_build(build_id)

        if build.is_ended:
            if (build.proto.status == status
                    and build.result_details == result_details
                    and build.url == url):
                return False, build
            raise errors.BuildIsCompletedError(
                'Build %s has already completed' % build_id)
        _check_lease(build, lease_key)

        now = utils.utcnow()
        build.proto.status = status
        build.status_changed_time = now
        build.proto.end_time.FromDatetime(now)
        if url is not None:  # pragma: no branch
            build.url = url
        build.result_details = result_details
        if new_tags:
            build.tags.extend(new_tags)
            build.tags = sorted(set(build.tags))
        build.clear_lease()

        _fut_results(
            build.put_async(),
            events.on_build_completing_async(build),
            _put_output_properties_async(build.key, result_details),
        )
        return True, build

    updated, build = txn()
    if updated:
        events.on_build_completed(build)
    return build
Пример #4
0
def _end_build(build_id, status, summary_markdown='', end_time=None):
    assert model.is_terminal_status(status)
    end_time = end_time or utils.utcnow()

    @ndb.transactional
    def txn():
        build = model.Build.get_by_id(build_id)
        if not build:  # pragma: no cover
            return None

        build.proto.status = status
        build.proto.summary_markdown = summary_markdown
        build.proto.end_time.FromDatetime(end_time)
        ndb.Future.wait_all(
            [build.put_async(),
             events.on_build_completing_async(build)])
        return build

    build = txn()
    if build:  # pragma: no branch
        events.on_build_completed(build)
Пример #5
0
def _sync_build_async(build_id, result):
    """Syncs Build entity in the datastore with the task result."""
    @ndb.transactional_tasklet
    def txn_async():
        build = yield model.Build.get_by_id_async(build_id)
        if build is None or not _sync_build_in_memory(build, result):
            raise ndb.Return(None)

        futures = [build.put_async()]

        if build.status == model.BuildStatus.STARTED:
            futures.append(events.on_build_starting_async(build))
        elif build.status == model.BuildStatus.COMPLETED:  # pragma: no branch
            futures.append(events.on_build_completing_async(build))

        yield futures
        raise ndb.Return(build)

    build = yield txn_async()
    if build:
        if build.status == model.BuildStatus.STARTED:
            events.on_build_started(build)
        elif build.status == model.BuildStatus.COMPLETED:  # pragma: no branch
            events.on_build_completed(build)
Пример #6
0
def update_build_async(req, _res, ctx, _mask):
    """Update build as in given request.

  For now, only update build steps.

  Does not mutate res.
  In practice, clients does not need the response, they just want to provide
  the data.
  """
    now = utils.utcnow()
    logging.debug('updating build %d', req.build.id)

    # Validate the request.
    build_steps = model.BuildSteps.make(req.build)
    validation.validate_update_build_request(req, build_steps)

    update_paths = set(req.update_mask.paths)

    if not (yield user.can_update_build_async()):
        raise StatusError(
            prpc.StatusCode.PERMISSION_DENIED,
            '%s not permitted to update build' %
            auth.get_current_identity().to_bytes())

    @ndb.tasklet
    def get_async():
        build = yield model.Build.get_by_id_async(req.build.id)
        if not build:
            raise not_found('Cannot update nonexisting build with id %s',
                            req.build.id)
        if build.is_ended:
            raise failed_precondition('Cannot update an ended build')

        # Ensure a SCHEDULED build does not have steps or output.
        final_status = (req.build.status if 'build.status' in update_paths else
                        build.proto.status)
        if final_status == common_pb2.SCHEDULED:
            if 'build.steps' in update_paths:
                raise invalid_argument(
                    'cannot update steps of a SCHEDULED build; '
                    'either set status to non-SCHEDULED or do not update steps'
                )
            if any(p.startswith('build.output.') for p in update_paths):
                raise invalid_argument(
                    'cannot update build output fields of a SCHEDULED build; '
                    'either set status to non-SCHEDULED or do not update build output'
                )

        raise ndb.Return(build)

    build = yield get_async()
    validate_build_token(build, ctx)

    # Prepare a field mask to merge req.build into model.Build.proto.
    # Exclude fields that are stored elsewhere.
    # Note that update_paths was (indirectly) validated by validation.py
    # against a whitelist.
    model_build_proto_mask = protoutil.Mask.from_field_mask(
        field_mask_pb2.FieldMask(
            paths=list(update_paths -
                       {'build.steps', 'build.output.properties'})),
        rpc_pb2.UpdateBuildRequest.DESCRIPTOR,
        update_mask=True,
    ).submask('build')

    out_prop_bytes = req.build.output.properties.SerializeToString()

    @ndb.transactional_tasklet
    def txn_async():
        build = yield get_async()

        orig_status = build.status

        futures = []

        if 'build.output.properties' in update_paths:
            futures.append(
                model.BuildOutputProperties(
                    key=model.BuildOutputProperties.key_for(build.key),
                    properties=out_prop_bytes,
                ).put_async())

        if model_build_proto_mask:  # pragma: no branch
            # Merge the rest into build.proto using model_build_proto_mask.
            model_build_proto_mask.merge(req.build, build.proto)

        # If we are updating build status, update some other dependent fields
        # and schedule notifications.
        status_changed = orig_status != build.proto.status
        if status_changed:
            if build.proto.status == common_pb2.STARTED:
                if not build.proto.HasField('start_time'):  # pragma: no branch
                    build.proto.start_time.FromDatetime(now)
                futures.append(events.on_build_starting_async(build))
            else:
                assert model.is_terminal_status(
                    build.proto.status), build.proto.status
                build.clear_lease()
                if not build.proto.HasField('end_time'):  # pragma: no branch
                    build.proto.end_time.FromDatetime(now)
                futures.append(events.on_build_completing_async(build))

        if 'build.steps' in update_paths:
            # TODO(crbug.com/936892): reject requests with a terminal build status
            # and incomplete steps, when
            # https://chromium-review.googlesource.com/c/infra/infra/+/1553291
            # is deployed.
            futures.append(build_steps.put_async())
        elif build.is_ended:
            futures.append(
                model.BuildSteps.cancel_incomplete_steps_async(
                    req.build.id, build.proto.end_time))

        futures.append(build.put_async())
        yield futures
        raise ndb.Return(build, status_changed)

    build, status_changed = yield txn_async()
    if status_changed:
        if build.proto.status == common_pb2.STARTED:
            events.on_build_started(build)
        else:
            assert model.is_terminal_status(
                build.proto.status), build.proto.status
            events.on_build_completed(build)
Пример #7
0
def cancel_async(build_id, summary_markdown='', result_details=None):
    """Cancels build. Does not require a lease key.

  The current user has to have a permission to cancel a build in the
  bucket.

  Args:
    build_id: id of the build to cancel.
    summary_markdown (basestring): human-readable explanation.
    result_details (dict): build result description.

  Returns:
    Canceled Build.
  """
    identity_str = auth.get_current_identity().to_bytes()

    @ndb.tasklet
    def get_build_async(check_access):
        build = yield model.Build.get_by_id_async(build_id)
        if build is None:
            raise errors.BuildNotFoundError()
        if check_access and not (yield user.can_cancel_build_async(build)):
            raise user.current_identity_cannot('cancel build %s',
                                               build.key.id())
        if build.proto.status == common_pb2.CANCELED:
            raise ndb.Return(build, False)
        if build.is_ended:
            raise errors.BuildIsCompletedError(
                'Cannot cancel a completed build')
        raise ndb.Return(build, True)

    @ndb.transactional_tasklet
    def txn_async():
        build, should_update = yield get_build_async(False)
        if not should_update:  # pragma: no cover
            raise ndb.Return(build, False)
        now = utils.utcnow()
        build.proto.status = common_pb2.CANCELED
        build.status_changed_time = now
        build.result_details = result_details
        build.proto.summary_markdown = summary_markdown
        build.proto.canceled_by = identity_str
        build.proto.end_time.FromDatetime(now)
        build.clear_lease()
        futs = [
            build.put_async(),
            events.on_build_completing_async(build),
            _put_output_properties_async(build.key, result_details),
            model.BuildSteps.cancel_incomplete_steps_async(
                build.key.id(), build.proto.end_time)
        ]

        sw = build.parse_infra().swarming
        # TODO(nodir): remove, in favor of swarming.TaskSyncBuild.
        if sw.hostname and sw.task_id:  # pragma: no branch
            futs.append(
                swarming.cancel_task_transactionally_async(
                    sw.hostname, sw.task_id))
        yield futs
        raise ndb.Return(build, True)

    build, should_update = yield get_build_async(True)
    if should_update:
        build, updated = yield txn_async()
        if updated:  # pragma: no branch
            events.on_build_completed(build)
    raise ndb.Return(build)
Пример #8
0
 def update_async(build_key):
   # This is the only yield in this function, but it is not
   # performance-critical.
   updated, build = yield txn_async(build_key)
   if updated:  # pragma: no branch
     events.on_build_completed(build)