async def test_diffs_logged_if_present(registry, resource, handlers, cause_type, cause_mock, caplog, assert_logs, diff): caplog.set_level(logging.DEBUG) event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type cause_mock.diff = diff cause_mock.new = object() # checked for `not None` cause_mock.old = object() # checked for `not None` await resource_handler( lifecycle=kopf.lifecycles.all_at_once, registry=registry, resource=resource, memories=ResourceMemories(), event={ 'type': event_type, 'object': cause_mock.body }, freeze=asyncio.Event(), replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert_logs([" event: ", " diff: "])
async def test_handlers_called_always( registry, settings, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = cause_type await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={'type': 'ev-type', 'object': {'field': 'value'}}, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert handlers.event_mock.call_count == 1 assert extrahandlers.event_mock.call_count == 1 event = handlers.event_mock.call_args_list[0][1]['event'] assert 'field' in event['object'] assert event['object']['field'] == 'value' assert event['type'] == 'ev-type' assert_logs([ "Handler 'event_fn' is invoked.", "Handler 'event_fn' succeeded.", "Handler 'event_fn2' is invoked.", "Handler 'event_fn2' succeeded.", ])
async def test_stealth_mode_with_mismatching_handlers( registry, settings, selector, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked, annotations, labels, when, deleted, initial): caplog.set_level(logging.DEBUG) event_type = None event_body = {'metadata': {'finalizers': []}} cause_mock.reason = cause_type assert not registry._resource_changing.has_handlers(resource=resource) # prerequisite registry._resource_changing.append(ResourceChangingHandler( reason=None, fn=lambda **_: None, id='id', errors=None, timeout=None, retries=None, backoff=None, selector=selector, annotations=annotations, labels=labels, when=when, field=None, value=None, old=None, new=None, field_needs_change=None, deleted=deleted, initial=initial, requires_finalizer=None, )) await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={'type': event_type, 'object': event_body}, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert not k8s_mocked.sleep_or_wait.called assert not k8s_mocked.patch_obj.called assert not caplog.messages # total stealth mode!
async def test_retries_limited_handler_fails(registry, settings, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) name1 = f'{cause_type}_fn' event_type = None if cause_type == Reason.RESUME else 'irrelevant' event_body = { 'status': { 'kopf': { 'progress': { 'create_fn': { 'retries': 100 }, 'update_fn': { 'retries': 100 }, 'delete_fn': { 'retries': 100 }, 'resume_fn': { 'retries': 100 }, } } } } cause_mock.reason = cause_type await process_resource_event( lifecycle=kopf.lifecycles.one_by_one, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': event_body }, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert not handlers.create_mock.called assert not handlers.update_mock.called assert not handlers.delete_mock.called assert not handlers.resume_mock.called # Progress is reset, as the handler is not going to retry. assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert patch['status']['kopf']['progress'] is not None assert patch['status']['kopf']['progress'][name1]['failure'] is True assert_logs([ r"Handler .+ has exceeded \d+ retries", ])
async def test_reporting_on_resource_readiness(resource, settings, registry, indexers, caplog, event_type, handlers, timer): caplog.set_level(logging.DEBUG) operator_indexed = ToggleSet(all) resource_indexed = await operator_indexed.make_toggle() async with timer, async_timeout.timeout(0.5) as timeout: await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), operator_indexed=operator_indexed, resource_indexed=resource_indexed, ) assert not timeout.expired assert timer.seconds < 0.2 # asap, nowait assert operator_indexed.is_on() assert set(operator_indexed) == set() # save RAM assert handlers.event_mock.called
async def test_preserved_on_logical_deletion(resource, settings, registry, indexers, index, caplog, event_type, handlers): caplog.set_level(logging.DEBUG) body = { 'metadata': { 'namespace': 'ns1', 'name': 'name1', 'deletionTimestamp': '...' } } handlers.index_mock.return_value = 456 await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': body }, event_queue=asyncio.Queue(), resource_indexed=Toggle(), # used! only to enable indexing. ) assert set(index) == {None} assert set(index[None]) == {456}
async def test_gone(registry, handlers, resource, cause_mock, event_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = Reason.GONE event_queue = asyncio.Queue() await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': {} }, replenished=asyncio.Event(), event_queue=event_queue, ) assert not handlers.create_mock.called assert not handlers.update_mock.called assert not handlers.delete_mock.called assert not k8s_mocked.asyncio_sleep.called assert not k8s_mocked.sleep_or_wait.called assert not k8s_mocked.patch_obj.called assert event_queue.empty() assert_logs([ "Deleted, really deleted", ])
async def test_diffs_logged_if_present(registry, settings, resource, handlers, cause_type, cause_mock, caplog, assert_logs, diff): caplog.set_level(logging.DEBUG) event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type cause_mock.diff = diff cause_mock.new = { 'field': 'old' } # checked for `not None`, and JSON-serialised cause_mock.old = { 'field': 'new' } # checked for `not None`, and JSON-serialised await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': {} }, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert_logs([" event: ", " diff: "])
async def test_supersession_is_logged( registry, settings, resource, handlers, cause_types, cause_mock, caplog, assert_logs): caplog.set_level(logging.DEBUG) settings.persistence.progress_storage = StatusProgressStorage() body = {'status': {'kopf': {'progress': { 'create_fn': {'purpose': cause_types[0]}, 'update_fn': {'purpose': cause_types[0]}, 'resume_fn': {'purpose': cause_types[0]}, 'delete_fn': {'purpose': cause_types[0]}, }}}} cause_mock.reason = cause_types[1] event_type = None if cause_types[1] == Reason.RESUME else 'irrelevant' await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={'type': event_type, 'object': body}, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert_logs([ "(Creation|Updating|Resuming|Deletion) is superseded by (creation|updating|resuming|deletion): ", "(Creation|Updating|Resuming|Deletion) is in progress: ", "(Creation|Updating|Resuming|Deletion) is processed: ", ])
async def test_errors_are_ignored(registry, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = cause_type handlers.event_mock.side_effect = Exception("oops") await resource_handler( lifecycle=kopf.lifecycles.all_at_once, registry=registry, resource=resource, memories=ResourceMemories(), event={ 'type': 'ev-type', 'object': cause_mock.body }, freeze=asyncio.Event(), replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert handlers.event_mock.called assert extrahandlers.event_mock.called assert_logs([ "Invoking handler 'event_fn'.", "Handler 'event_fn' failed with an exception. Will ignore.", "Invoking handler 'event_fn2'.", "Handler 'event_fn2' succeeded.", ])
async def test_diffs_logged_if_present(registry, settings, resource, handlers, cause_type, cause_mock, caplog, assert_logs, diff): caplog.set_level(logging.DEBUG) event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type cause_mock.diff = diff cause_mock.new = { 'field': 'old' } # checked for `not None`, and JSON-serialised cause_mock.old = { 'field': 'new' } # checked for `not None`, and JSON-serialised await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), ) assert_logs([ "(Creation|Updating|Resuming|Deletion) is in progress: ", "(Creation|Updating|Resuming|Deletion) diff: " ])
async def test_handlers_called_always(registry, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = cause_type await resource_handler( lifecycle=kopf.lifecycles.all_at_once, registry=registry, resource=resource, memories=ResourceMemories(), event={ 'type': 'ev-type', 'object': cause_mock.body }, freeze=asyncio.Event(), replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert handlers.event_mock.call_count == 1 assert extrahandlers.event_mock.call_count == 1 event = handlers.event_mock.call_args_list[0][1]['event'] assert event['type'] == 'ev-type' assert event['object'] is cause_mock.body assert event['type'] == 'ev-type' assert event['object'] is cause_mock.body assert_logs([ "Invoking handler 'event_fn'.", "Handler 'event_fn' succeeded.", "Invoking handler 'event_fn2'.", "Handler 'event_fn2' succeeded.", ])
async def test_errors_are_ignored(registry, settings, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = cause_type handlers.event_mock.side_effect = Exception("oops") await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': 'ev-type', 'object': {} }, event_queue=asyncio.Queue(), ) assert handlers.event_mock.called assert extrahandlers.event_mock.called assert_logs([ "Handler 'event_fn' is invoked.", "Handler 'event_fn' failed with an exception. Will ignore.", "Handler 'event_fn2' is invoked.", "Handler 'event_fn2' succeeded.", ])
async def test_timed_out_handler_fails(registry, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked, now, ts): caplog.set_level(logging.DEBUG) name1 = f'{cause_type}_fn' event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type cause_mock.body.update({ 'status': { 'kopf': { 'progress': { 'create_fn': { 'started': ts }, 'update_fn': { 'started': ts }, 'delete_fn': { 'started': ts }, 'resume_fn': { 'started': ts }, } } } }) with freezegun.freeze_time(now): await process_resource_event( lifecycle=kopf.lifecycles.one_by_one, registry=registry, resource=resource, memories=ResourceMemories(), event={ 'type': event_type, 'object': cause_mock.body }, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert not handlers.create_mock.called assert not handlers.update_mock.called assert not handlers.delete_mock.called assert not handlers.resume_mock.called # Progress is reset, as the handler is not going to retry. assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert patch['status']['kopf']['progress'] is not None assert patch['status']['kopf']['progress'][name1]['failure'] is True assert_logs([ "Handler .+ has timed out after", ])
async def test_diffs_not_logged_if_absent(registry, settings, resource, handlers, cause_type, cause_mock, caplog, assert_logs, diff): caplog.set_level(logging.DEBUG) event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type cause_mock.diff = diff await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), ) assert_logs([ "(Creation|Updating|Resuming|Deletion) is in progress: ", ], prohibited=[" diff: "])
async def test_free(registry, handlers, resource, cause_mock, event_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = Reason.FREE event_queue = asyncio.Queue() await resource_handler( lifecycle=kopf.lifecycles.all_at_once, registry=registry, resource=resource, memories=ResourceMemories(), event={ 'type': event_type, 'object': cause_mock.body }, replenished=asyncio.Event(), event_queue=event_queue, ) assert not handlers.create_mock.called assert not handlers.update_mock.called assert not handlers.delete_mock.called assert not k8s_mocked.asyncio_sleep.called assert not k8s_mocked.sleep_or_wait.called assert not k8s_mocked.patch_obj.called assert event_queue.empty() assert_logs([ "Deletion event, but we are done with it", ])
async def test_blocking_when_operator_is_not_ready(resource, settings, registry, indexers, caplog, event_type, handlers, timer): caplog.set_level(logging.DEBUG) operator_indexed = ToggleSet(all) resource_listed = await operator_indexed.make_toggle() resource_indexed = await operator_indexed.make_toggle() with pytest.raises(asyncio.TimeoutError): async with timer, async_timeout.timeout(0.2) as timeout: await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), operator_indexed=operator_indexed, resource_indexed=resource_indexed, ) assert timeout.expired assert 0.2 < timer.seconds < 0.4 assert operator_indexed.is_off() assert set(operator_indexed) == {resource_listed} assert not handlers.event_mock.called
async def test_unblocking_once_operator_is_ready(resource, settings, registry, indexers, caplog, event_type, handlers, timer): caplog.set_level(logging.DEBUG) async def delayed_readiness(delay: float): await asyncio.sleep(delay) await resource_listed.turn_to(True) operator_indexed = ToggleSet(all) resource_listed = await operator_indexed.make_toggle() resource_indexed = await operator_indexed.make_toggle() async with timer, async_timeout.timeout(1.0) as timeout: asyncio.create_task(delayed_readiness(0.2)) await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), operator_indexed=operator_indexed, resource_indexed=resource_indexed, ) assert not timeout.expired assert 0.2 < timer.seconds < 0.4 assert operator_indexed.is_on() assert set(operator_indexed) == {resource_listed} assert handlers.event_mock.called
async def test_skipped_with_no_handlers(registry, settings, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) event_type = None event_body = {'metadata': {'finalizers': []}} cause_mock.reason = cause_type assert not registry.resource_changing_handlers[resource] # prerequisite registry.resource_changing_handlers[resource].append( ResourceChangingHandler( reason='a-non-existent-cause-type', fn=lambda **_: None, id='id', errors=None, timeout=None, retries=None, backoff=None, cooldown=None, annotations=None, labels=None, when=None, field=None, deleted=None, initial=None, requires_finalizer=None, )) await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': event_body }, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called # The patch must contain ONLY the last-seen update, and nothing else. patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert set(patch.keys()) == {'metadata'} assert set(patch['metadata'].keys()) == {'annotations'} assert set( patch['metadata']['annotations'].keys()) == {LAST_SEEN_ANNOTATION} assert_logs([ ".* event:", "Patching with:", ], prohibited=[ "event is processed:", ])
async def test_removed_on_filters_mismatch(resource, settings, registry, indexers, index, caplog, event_type, handlers, mocker): # Simulate the indexing handler is gone out of scope (this is only one of the ways to do it): mocker.patch.object(registry._resource_indexing, 'get_handlers', return_value=[]) caplog.set_level(logging.DEBUG) body = {'metadata': {'namespace': 'ns1', 'name': 'name1'}} handlers.index_mock.return_value = 123 await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': body }, event_queue=asyncio.Queue(), resource_indexed=Toggle(), # used! only to enable indexing. ) assert set(index) == set()
async def test_2nd_step_finishes_the_handlers(caplog, registry, handlers, extrahandlers, resource, cause_mock, cause_type, k8s_mocked): name1 = f'{cause_type}_fn' name2 = f'{cause_type}_fn2' event_type = None if cause_type == Reason.RESUME else 'irrelevant' event_body = { 'status': { 'kopf': { 'progress': { 'resume_fn': { 'started': '1979-01-01T00:00:00', 'success': True }, 'resume_fn2': { 'started': '1979-01-01T00:00:00', 'success': True }, name1: { 'started': '1979-01-01T00:00:00', 'success': True }, name2: { 'started': '1979-01-01T00:00:00' }, } } } } cause_mock.reason = cause_type await process_resource_event( lifecycle=kopf.lifecycles.one_by_one, registry=registry, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': event_body }, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert extrahandlers.create_mock.call_count == (1 if cause_type == Reason.CREATE else 0) assert extrahandlers.update_mock.call_count == (1 if cause_type == Reason.UPDATE else 0) assert extrahandlers.delete_mock.call_count == (1 if cause_type == Reason.DELETE else 0) assert extrahandlers.resume_mock.call_count == (1 if cause_type == Reason.RESUME else 0) assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert patch['status']['kopf']['progress'] is None
async def test_parameter_is_passed_when_specified(resource, cause_mock, registry, settings): mock = Mock() # If it works for this handler, we assume it works for all of them. # Otherwise, it is too difficult to trigger the actual invocation. @kopf.on.event(*resource, param=123) def fn(**kwargs): mock(**kwargs) event_queue = asyncio.Queue() await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={ 'type': None, 'object': {} }, replenished=asyncio.Event(), event_queue=event_queue, ) assert mock.called assert mock.call_args_list[0][1]['param'] == 123
async def test_free(registry, settings, handlers, resource, cause_mock, event_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = Reason.FREE event_queue = asyncio.Queue() await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=event_queue, ) assert not handlers.create_mock.called assert not handlers.update_mock.called assert not handlers.delete_mock.called assert not k8s_mocked.sleep_or_wait.called assert not k8s_mocked.patch_obj.called assert event_queue.empty() assert_logs([ "Deletion, but we are done with it", ])
async def test_noop(registry, settings, handlers, resource, cause_mock, event_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) cause_mock.reason = Reason.NOOP event_queue = asyncio.Queue() await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': {} }, replenished=asyncio.Event(), event_queue=event_queue, ) assert not handlers.create_mock.called assert not handlers.update_mock.called assert not handlers.delete_mock.called assert not k8s_mocked.sleep_or_wait.called assert not k8s_mocked.patch_obj.called assert event_queue.empty() assert_logs([ "Something has changed, but we are not interested", ])
async def test_diffs_not_logged_if_absent(registry, settings, resource, handlers, cause_type, cause_mock, caplog, assert_logs, diff): caplog.set_level(logging.DEBUG) event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type cause_mock.diff = diff await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, memories=ResourceMemories(), raw_event={ 'type': event_type, 'object': {} }, replenished=asyncio.Event(), event_queue=asyncio.Queue(), ) assert_logs([ " event: ", ], prohibited=[" diff: "])
async def test_1st_step_stores_progress_by_patching(registry, settings, handlers, extrahandlers, resource, cause_mock, cause_type, k8s_mocked, deletion_ts): name1 = f'{cause_type}_fn' name2 = f'{cause_type}_fn2' event_type = None if cause_type == Reason.RESUME else 'irrelevant' event_body = { 'metadata': { 'finalizers': [settings.persistence.finalizer] }, } event_body['metadata'].update(deletion_ts) cause_mock.reason = cause_type await process_resource_event( lifecycle=kopf.lifecycles.asap, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': event_body }, event_queue=asyncio.Queue(), ) assert handlers.create_mock.call_count == (1 if cause_type == Reason.CREATE else 0) assert handlers.update_mock.call_count == (1 if cause_type == Reason.UPDATE else 0) assert handlers.delete_mock.call_count == (1 if cause_type == Reason.DELETE else 0) assert handlers.resume_mock.call_count == (1 if cause_type == Reason.RESUME else 0) assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert patch['status']['kopf']['progress'] is not None assert patch['status']['kopf']['progress'][name1]['retries'] == 1 assert patch['status']['kopf']['progress'][name1]['success'] is True assert patch['status']['kopf']['progress'][name2]['retries'] == 0 assert patch['status']['kopf']['progress'][name2]['success'] is False assert patch['status']['kopf']['progress'][name1]['started'] assert patch['status']['kopf']['progress'][name2]['started'] # Premature removal of finalizers can prevent the 2nd step for deletion handlers. # So, the finalizers must never be removed on the 1st step. assert 'finalizers' not in patch['metadata']
async def test_forgetting_deletes_when_present(): memories = ResourceMemories() memory1 = await memories.recall(BODY) await memories.forget(BODY) # Check by recalling -- it should be a new one. memory2 = await memories.recall(BODY) assert memory1 is not memory2
async def test_memo_is_shallow_copied(): class MyMemo(Memo): def __copy__(self): mock() return MyMemo() mock = Mock() memo = MyMemo() memories = ResourceMemories() memory = await memories.recall(BODY, memo=memo) assert mock.call_count == 1 assert memory.memo is not memo
async def test_retry_error_delays_handler(registry, settings, handlers, extrahandlers, resource, cause_mock, cause_type, caplog, assert_logs, k8s_mocked): caplog.set_level(logging.DEBUG) name1 = f'{cause_type}_fn' event_type = None if cause_type == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_type handlers.create_mock.side_effect = TemporaryError("oops") handlers.update_mock.side_effect = TemporaryError("oops") handlers.delete_mock.side_effect = TemporaryError("oops") handlers.resume_mock.side_effect = TemporaryError("oops") await process_resource_event( lifecycle=kopf.lifecycles.one_by_one, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), ) assert handlers.create_mock.call_count == (1 if cause_type == Reason.CREATE else 0) assert handlers.update_mock.call_count == (1 if cause_type == Reason.UPDATE else 0) assert handlers.delete_mock.call_count == (1 if cause_type == Reason.DELETE else 0) assert handlers.resume_mock.call_count == (1 if cause_type == Reason.RESUME else 0) assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert patch['status']['kopf']['progress'] is not None assert patch['status']['kopf']['progress'][name1]['failure'] is False assert patch['status']['kopf']['progress'][name1]['success'] is False assert patch['status']['kopf']['progress'][name1]['delayed'] assert_logs([ "Handler .+ failed temporarily: oops", ])
async def test_delayed_handlers_progress(registry, settings, handlers, resource, cause_mock, cause_reason, caplog, assert_logs, k8s_mocked, now, delayed_iso, delay): caplog.set_level(logging.DEBUG) handlers.create_mock.side_effect = TemporaryError("oops", delay=delay) handlers.update_mock.side_effect = TemporaryError("oops", delay=delay) handlers.delete_mock.side_effect = TemporaryError("oops", delay=delay) handlers.resume_mock.side_effect = TemporaryError("oops", delay=delay) event_type = None if cause_reason == Reason.RESUME else 'irrelevant' cause_mock.reason = cause_reason with freezegun.freeze_time(now): await process_resource_event( lifecycle=kopf.lifecycles.all_at_once, registry=registry, settings=settings, resource=resource, indexers=OperatorIndexers(), memories=ResourceMemories(), memobase=Memo(), raw_event={ 'type': event_type, 'object': {} }, event_queue=asyncio.Queue(), ) assert handlers.create_mock.call_count == (1 if cause_reason == Reason.CREATE else 0) assert handlers.update_mock.call_count == (1 if cause_reason == Reason.UPDATE else 0) assert handlers.delete_mock.call_count == (1 if cause_reason == Reason.DELETE else 0) assert handlers.resume_mock.call_count == (1 if cause_reason == Reason.RESUME else 0) assert not k8s_mocked.sleep_or_wait.called assert k8s_mocked.patch_obj.called fname = f'{cause_reason}_fn' patch = k8s_mocked.patch_obj.call_args_list[0][1]['patch'] assert patch['status']['kopf']['progress'][fname]['delayed'] == delayed_iso assert_logs([ "Handler .+ is invoked", "Handler .+ failed temporarily: oops", ])