def test_on_create_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) mocker.patch('kopf.reactor.registries.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_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.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'}
def test_on_field_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.field('group', 'version', 'plural', 'field.subfield', id='id', timeout=123, registry=registry, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].timeout == 123 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
def test_on_delete_with_all_kwargs(mocker, optional): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.DELETE) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.delete('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, optional=optional, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].cooldown == 78 # deprecated alias assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
def test_on_field_with_all_kwargs( mocker, cause_factory): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = cause_factory(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.field('group', 'version', 'plural', 'field.subfield', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass with pytest.deprecated_call(match=r"use registry.resource_changing_handlers"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field ==('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
def test_on_create_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.create('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass with pytest.deprecated_call(match=r"use registry.resource_changing_handlers"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
async def handle_resource_changing_cause( lifecycle: lifecycles.LifeCycleFn, registry: registries.OperatorRegistry, memory: containers.ResourceMemory, cause: causation.ResourceChangingCause, ) -> Optional[float]: """ Handle a detected cause, as part of the bigger handler routine. """ logger = cause.logger patch = cause.patch # TODO get rid of this alias body = cause.body # TODO get rid of this alias delay = None done = None skip = None # Regular causes invoke the handlers. if cause.reason in causation.HANDLER_REASONS: title = causation.TITLES.get(cause.reason, repr(cause.reason)) logger.debug(f"{title.capitalize()} event: %r", body) if cause.diff and cause.old is not None and cause.new is not None: logger.debug(f"{title.capitalize()} diff: %r", cause.diff) handlers = registry.get_resource_changing_handlers(cause=cause) state = states.State.from_body(body=cause.body, handlers=handlers) if handlers: outcomes = await _execute_handlers( lifecycle=lifecycle, handlers=handlers, cause=cause, state=state, ) state = state.with_outcomes(outcomes) state.store(patch=cause.patch) states.deliver_results(outcomes=outcomes, patch=cause.patch) if state.done: logger.info(f"All handlers succeeded for {title}.") state.purge(patch=cause.patch, body=cause.body) done = state.done delay = state.delay else: skip = True # Regular causes also do some implicit post-handling when all handlers are done. if done or skip: extra_fields = registry.get_extra_fields(resource=cause.resource) lastseen.refresh_essence(body=body, patch=patch, extra_fields=extra_fields) if cause.reason == causation.Reason.DELETE: logger.debug("Removing the finalizer, thus allowing the actual deletion.") finalizers.remove_finalizers(body=body, patch=patch) # Once all handlers have succeeded at least once for any reason, or if there were none, # prevent further resume-handlers (which otherwise happens on each watch-stream re-listing). memory.fully_handled_once = True # Informational causes just print the log lines. if cause.reason == causation.Reason.GONE: logger.debug("Deleted, really deleted, and we are notified.") if cause.reason == causation.Reason.FREE: logger.debug("Deletion event, but we are done with it, and we do not care.") if cause.reason == causation.Reason.NOOP: logger.debug("Something has changed, but we are not interested (the essence is the same).") # For the case of a newly created object, or one that doesn't have the correct # finalizers, lock it to this operator. Not all newly created objects will # produce an 'ACQUIRE' causation event. This only happens when there are # mandatory deletion handlers registered for the given object, i.e. if finalizers # are required. if cause.reason == causation.Reason.ACQUIRE: logger.debug("Adding the finalizer, thus preventing the actual deletion.") finalizers.append_finalizers(body=body, patch=patch) # Remove finalizers from an object, since the object currently has finalizers, but # shouldn't, thus releasing the locking of the object to this operator. if cause.reason == causation.Reason.RELEASE: logger.debug("Removing the finalizer, as there are no handlers requiring it.") finalizers.remove_finalizers(body=body, patch=patch) # The delay is then consumed by the main handling routine (in different ways). return delay