예제 #1
0
async def handle_event(
    registry: registries.BaseRegistry,
    resource: registries.Resource,
    logger: logging_engine.ObjectLogger,
    patch: dict,
    event: dict,
):
    """
    Handle a received event, log but ignore all errors.

    This is a lightweight version of the cause handling, but for the raw events,
    without any progress persistence. Multi-step calls are also not supported.
    If the handler fails, it fails and is never retried.

    Note: K8s-event posting is skipped for `kopf.on.event` handlers,
    as they should be silent. Still, the messages are logged normally.
    """
    handlers = registry.get_event_handlers(resource=resource, event=event)
    for handler in handlers:

        # The exceptions are handled locally and are not re-raised, to keep the operator running.
        try:
            logger.debug(f"Invoking handler {handler.id!r}.")

            # TODO: also set the context-vars, despite most of the make no sense here.
            result = await invocation.invoke(
                handler.fn,
                event=event,
                patch=patch,
                logger=logger,
            )

        except Exception:
            logger.exception(
                f"Handler {handler.id!r} failed with an exception. Will ignore.",
                local=True)

        else:
            logger.info(f"Handler {handler.id!r} succeeded.", local=True)
            status.store_result(patch=patch, handler=handler, result=result)
예제 #2
0
async def apply_reaction_outcomes(
    *,
    settings: configuration.OperatorSettings,
    resource: resources.Resource,
    body: bodies.Body,
    patch: patches.Patch,
    delays: Collection[float],
    logger: logging_engine.ObjectLogger,
    replenished: asyncio.Event,
) -> None:
    delay = min(delays) if delays else None

    # Delete dummies on occasion, but don't trigger special patching for them [discussable].
    if patch:  # TODO: LATER: and the dummies are there (without additional methods?)
        settings.persistence.progress_storage.touch(body=body,
                                                    patch=patch,
                                                    value=None)

    # Actually patch if it contained payload originally or after dummies removal.
    if patch:
        logger.debug("Patching with: %r", patch)
        await patching.patch_obj(resource=resource, patch=patch, body=body)

    # Sleep strictly after patching, never before -- to keep the status proper.
    # The patching above, if done, interrupts the sleep instantly, so we skip it at all.
    # Note: a zero-second or negative sleep is still a sleep, it will trigger a dummy patch.
    if delay and patch:
        logger.debug(
            f"Sleeping was skipped because of the patch, {delay} seconds left."
        )
    elif delay is None and not patch:
        logger.debug(
            f"Handling cycle is finished, waiting for new changes since now.")
    elif delay is not None:
        if delay > WAITING_KEEPALIVE_INTERVAL:
            limit = WAITING_KEEPALIVE_INTERVAL
            logger.debug(
                f"Sleeping for {delay} (capped {limit}) seconds for the delayed handlers."
            )
            unslept_delay = await sleeping.sleep_or_wait(limit, replenished)
        elif delay > 0:
            logger.debug(
                f"Sleeping for {delay} seconds for the delayed handlers.")
            unslept_delay = await sleeping.sleep_or_wait(delay, replenished)
        else:
            unslept_delay = None  # no need to sleep? means: slept in full.

        # Exclude cases when touching immediately after patching (including: ``delay == 0``).
        if patch and not delay:
            pass
        elif unslept_delay is not None:
            logger.debug(
                f"Sleeping was interrupted by new changes, {unslept_delay} seconds left."
            )
        else:
            # Any unique always-changing value will work; not necessary a timestamp.
            value = datetime.datetime.utcnow().isoformat()
            touch_patch = patches.Patch()
            settings.persistence.progress_storage.touch(body=body,
                                                        patch=touch_patch,
                                                        value=value)
            if touch_patch:
                logger.debug("Provoking reaction with: %r", touch_patch)
                await patching.patch_obj(resource=resource,
                                         patch=touch_patch,
                                         body=body)
예제 #3
0
async def apply_reaction_outcomes(
    *,
    resource: resources.Resource,
    body: bodies.Body,
    patch: patches.Patch,
    delays: Collection[float],
    logger: logging_engine.ObjectLogger,
    replenished: asyncio.Event,
) -> None:
    delay = min(delays) if delays else None

    if patch:
        logger.debug("Patching with: %r", patch)
        await patching.patch_obj(resource=resource, patch=patch, body=body)

    # Sleep strictly after patching, never before -- to keep the status proper.
    # The patching above, if done, interrupts the sleep instantly, so we skip it at all.
    # Note: a zero-second or negative sleep is still a sleep, it will trigger a dummy patch.
    if delay and patch:
        logger.debug(
            f"Sleeping was skipped because of the patch, {delay} seconds left."
        )
    elif delay is None and not patch:
        logger.debug(
            f"Handling cycle is finished, waiting for new changes since now.")
    elif delay is not None:
        if delay > WAITING_KEEPALIVE_INTERVAL:
            limit = WAITING_KEEPALIVE_INTERVAL
            logger.debug(
                f"Sleeping for {delay} (capped {limit}) seconds for the delayed handlers."
            )
            unslept_delay = await sleeping.sleep_or_wait(limit, replenished)
        elif delay > 0:
            logger.debug(
                f"Sleeping for {delay} seconds for the delayed handlers.")
            unslept_delay = await sleeping.sleep_or_wait(delay, replenished)
        else:
            unslept_delay = None  # no need to sleep? means: slept in full.

        if unslept_delay is not None:
            logger.debug(
                f"Sleeping was interrupted by new changes, {unslept_delay} seconds left."
            )
        else:
            # Any unique always-changing value will work; not necessary a timestamp.
            dummy_value = datetime.datetime.utcnow().isoformat()
            dummy_patch = patches.Patch(
                {'status': {
                    'kopf': {
                        'dummy': dummy_value
                    }
                }})
            logger.debug("Provoking reaction with: %r", dummy_patch)
            await patching.patch_obj(resource=resource,
                                     patch=dummy_patch,
                                     body=body)