Exemplo n.º 1
0
def test_on_create_with_all_kwargs(mocker):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, event=CREATE)
    mocker.patch('kopf.structs.filters.match', return_value=True)

    @kopf.on.create('group',
                    'version',
                    'plural',
                    id='id',
                    timeout=123,
                    registry=registry,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'})
    def fn(**_):
        pass

    handlers = registry.get_cause_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].event == CREATE
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].timeout == 123
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert registry.requires_finalizer(resource=resource) is False
Exemplo n.º 2
0
def test_requires_finalizer_no_deletion_handler():
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')

    @kopf.on.create('group', 'version', 'plural', registry=registry)
    def fn1(**_):
        pass

    requires_finalizer = registry.requires_finalizer(resource=resource,
                                                     body=OBJECT_BODY)
    assert requires_finalizer is False
Exemplo n.º 3
0
def test_requires_finalizer_deletion_handler(optional, expected):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    registry=registry,
                    optional=optional)
    def fn(**_):
        pass

    requires_finalizer = registry.requires_finalizer(resource=resource,
                                                     body=OBJECT_BODY)
    assert requires_finalizer == expected
Exemplo n.º 4
0
def test_on_create_with_all_kwargs(mocker):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, event=CREATE)

    @kopf.on.create('group',
                    'version',
                    'plural',
                    id='id',
                    timeout=123,
                    registry=registry)
    def fn(**_):
        pass

    handlers = registry.get_cause_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].event == CREATE
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].timeout == 123
    assert registry.requires_finalizer(resource=resource) is False
Exemplo n.º 5
0
def test_on_field_with_all_kwargs(mocker):
    registry = GlobalRegistry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = mocker.MagicMock(resource=resource, event=UPDATE, diff=diff)

    @kopf.on.field('group',
                   'version',
                   'plural',
                   'field.subfield',
                   id='id',
                   timeout=123,
                   registry=registry)
    def fn(**_):
        pass

    handlers = registry.get_cause_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].event is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].timeout == 123
    assert registry.requires_finalizer(resource=resource) is False
Exemplo n.º 6
0
async def custom_object_handler(
        lifecycle: Callable,
        registry: registries.GlobalRegistry,
        resource: registries.Resource,
        event: dict,
        freeze: asyncio.Event,
        event_queue: asyncio.Queue,
) -> None:
    """
    Handle a single custom object low-level watch-event.

    Convert the low-level events, as provided by the watching/queueing tasks,
    to the high-level causes, and then call the cause-handling logic.

    All the internally provoked changes are intercepted, do not create causes,
    and therefore do not call the handling logic.
    """
    body = event['object']
    delay = None
    patch = {}

    # Each object has its own prefixed logger, to distinguish parallel handling.
    logger = ObjectLogger(logging.getLogger(__name__), extra=dict(
        namespace=body.get('metadata', {}).get('namespace', 'default'),
        name=body.get('metadata', {}).get('name', body.get('metadata', {}).get('uid', None)),
    ))
    posting.event_queue_var.set(event_queue)  # till the end of this object's task.

    # If the global freeze is set for the processing (i.e. other operator overrides), do nothing.
    if freeze.is_set():
        logger.debug("Ignoring the events due to freeze.")
        return

    # Invoke all silent spies. No causation, no progress storage is performed.
    if registry.has_event_handlers(resource=resource):
        await handle_event(registry=registry, resource=resource, event=event, logger=logger, patch=patch)

    # Object patch accumulator. Populated by the methods. Applied in the end of the handler.
    # Detect the cause and handle it (or at least log this happened).
    if registry.has_cause_handlers(resource=resource):
        extra_fields = registry.get_extra_fields(resource=resource)
        old, new, diff = lastseen.get_state_diffs(body=body, extra_fields=extra_fields)
        cause = causation.detect_cause(
            event=event,
            resource=resource,
            logger=logger,
            patch=patch,
            old=old,
            new=new,
            diff=diff,
            requires_finalizer=registry.requires_finalizer(resource=resource),
        )
        delay = await handle_cause(lifecycle=lifecycle, registry=registry, cause=cause)

    # Provoke a dummy change to trigger the reactor after sleep.
    # TODO: reimplement via the handler delayed statuses properly.
    if delay and not patch:
        patch.setdefault('status', {}).setdefault('kopf', {})['dummy'] = datetime.datetime.utcnow().isoformat()

    # Whatever was done, apply the accumulated changes to the object.
    # But only once, to reduce the number of API calls and the generated irrelevant events.
    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.
    if delay:
        logger.info(f"Sleeping for {delay} seconds for the delayed handlers.")
        await asyncio.sleep(delay)
Exemplo n.º 7
0
async def custom_object_handler(
    lifecycle: lifecycles.LifeCycleFn,
    registry: registries.GlobalRegistry,
    resource: resources.Resource,
    event: bodies.Event,
    freeze: asyncio.Event,
    replenished: asyncio.Event,
    event_queue: posting.K8sEventQueue,
) -> None:
    """
    Handle a single custom object low-level watch-event.

    Convert the low-level events, as provided by the watching/queueing tasks,
    to the high-level causes, and then call the cause-handling logic.

    All the internally provoked changes are intercepted, do not create causes,
    and therefore do not call the handling logic.
    """
    body: bodies.Body = event['object']
    patch: patches.Patch = patches.Patch()
    delay: Optional[float] = None

    # Each object has its own prefixed logger, to distinguish parallel handling.
    logger = logging_engine.ObjectLogger(body=body)
    posting.event_queue_loop_var.set(asyncio.get_running_loop())
    posting.event_queue_var.set(
        event_queue)  # till the end of this object's task.

    # If the global freeze is set for the processing (i.e. other operator overrides), do nothing.
    if freeze.is_set():
        logger.debug("Ignoring the events due to freeze.")
        return

    # Invoke all silent spies. No causation, no progress storage is performed.
    if registry.has_event_watching_handlers(resource=resource):
        event_watching_cause = causation.detect_event_watching_cause(
            event=event,
            resource=resource,
            logger=logger,
            patch=patch,
        )
        await handle_event_watching_cause(
            lifecycle=lifecycles.all_at_once,
            registry=registry,
            cause=event_watching_cause,
        )

    # Object patch accumulator. Populated by the methods. Applied in the end of the handler.
    # Detect the cause and handle it (or at least log this happened).
    if registry.has_state_changing_handlers(resource=resource):
        extra_fields = registry.get_extra_fields(resource=resource)
        old, new, diff = lastseen.get_essential_diffs(
            body=body, extra_fields=extra_fields)
        state_changing_cause = causation.detect_state_changing_cause(
            event=event,
            resource=resource,
            logger=logger,
            patch=patch,
            old=old,
            new=new,
            diff=diff,
            requires_finalizer=registry.requires_finalizer(resource=resource,
                                                           body=body),
        )
        delay = await handle_state_changing_cause(
            lifecycle=lifecycle,
            registry=registry,
            cause=state_changing_cause,
        )

    # Whatever was done, apply the accumulated changes to the object.
    # But only once, to reduce the number of API calls and the generated irrelevant events.
    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.
    if delay and not patch:
        logger.debug(f"Sleeping for {delay} seconds for the delayed handlers.")
        unslept = await sleeping.sleep_or_wait(delay, replenished)
        if unslept is not None:
            logger.debug(
                f"Sleeping was interrupted by new changes, {unslept} seconds left."
            )
        else:
            now = datetime.datetime.utcnow()
            dummy = patches.Patch(
                {'status': {
                    'kopf': {
                        'dummy': now.isoformat()
                    }
                }})
            logger.debug("Provoking reaction with: %r", dummy)
            await patching.patch_obj(resource=resource, patch=dummy, body=body)