Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
async def test_all_logs_are_prefixed(registry, resource, handlers,
logstream, cause_type, cause_mock):
event_type = None if cause_type == Reason.RESUME else 'irrelevant'
cause_mock.reason = cause_type
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=asyncio.Queue(),
)
lines = logstream.getvalue().splitlines()
assert lines # no messages means that we cannot test it
assert all(line.startswith('prefix [ns1/name1] ') for line in lines)
async def test_special_kwargs_added(fn, resource):
body = {'metadata': {'uid': 'uid', 'name': 'name', 'namespace': 'ns'},
'spec': {'field': 'value'},
'status': {'info': 'payload'}}
# Values can be any.
cause = ResourceChangingCause(
logger=logging.getLogger('kopf.test.fake.logger'),
resource=resource,
patch=Patch(),
initial=False,
reason=Reason.NOOP,
memo=object(),
body=body,
diff=object(),
old=object(),
new=object(),
)
fn = MagicMock(fn)
await invoke(fn, cause=cause)
assert fn.called
assert fn.call_count == 1
assert len(fn.call_args[1]) >= 2
assert fn.call_args[1]['cause'] is cause
assert fn.call_args[1]['event'] is cause.reason # deprecated
async def test_retries_limited_handler_fails(
registry, 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
cause_mock.body.update({
'status': {'kopf': {'progress': {
'create_fn': {'retries': 100},
'update_fn': {'retries': 100},
'delete_fn': {'retries': 100},
'resume_fn': {'retries': 100},
}}}
})
await resource_handler(
lifecycle=kopf.lifecycles.one_by_one,
registry=registry,
resource=resource,
memories=ResourceMemories(),
event={'type': event_type, 'object': cause_mock.body},
async def test_1st_step_stores_progress_by_patching(
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'
cause_mock.reason = cause_type
await resource_handler(
lifecycle=kopf.lifecycles.asap,
registry=registry,
resource=resource,
memories=ResourceMemories(),
event={'type': event_type, 'object': cause_mock.body},
replenished=asyncio.Event(),
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)
def test_for_gone(kwargs, event, finalizers, deletion_ts, requires_finalizer):
event = {'type': event, 'object': {'metadata': {}}}
event['object']['metadata'].update(finalizers)
event['object']['metadata'].update(deletion_ts)
cause = detect_resource_changing_cause(
event=event,
requires_finalizer=requires_finalizer,
**kwargs)
assert cause.reason == Reason.GONE
check_kwargs(cause, kwargs)
cause_mock.reason = cause_type
await resource_handler(
lifecycle=kopf.lifecycles.asap,
registry=registry,
resource=resource,
memories=ResourceMemories(),
event={'type': event_type, 'object': cause_mock.body},
replenished=asyncio.Event(),
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']
# The object was really deleted from the cluster. But we do not care anymore.
if event['type'] == 'DELETED':
return ResourceChangingCause(reason=Reason.GONE, **kwargs)
# The finalizer has been just removed. We are fully done.
if finalizers.is_deleted(body) and not finalizers.has_finalizers(body):
return ResourceChangingCause(reason=Reason.FREE, **kwargs)
if finalizers.is_deleted(body):
return ResourceChangingCause(reason=Reason.DELETE, **kwargs)
# For a fresh new object, first block it from accidental deletions without our permission.
# The actual handler will be called on the next call.
# Only return this cause if the resource requires finalizers to be added.
if requires_finalizer and not finalizers.has_finalizers(body):
return ResourceChangingCause(reason=Reason.ACQUIRE, **kwargs)
# Check whether or not the resource has finalizers, but doesn't require them. If this is
# the case, then a resource may not be able to be deleted completely as finalizers may
# not be removed by the operator under normal operation. We remove the finalizers first,
# and any handler that should be called will be done on the next call.
if not requires_finalizer and finalizers.has_finalizers(body):
return ResourceChangingCause(reason=Reason.RELEASE, **kwargs)
# For an object seen for the first time (i.e. just-created), call the creation handlers,
# then mark the state as if it was seen when the creation has finished.
# Creation never mixes with resuming, even if an object is detected on startup (first listing).
if not lastseen.has_essence_stored(body):
kwargs['initial'] = False
return ResourceChangingCause(reason=Reason.CREATE, **kwargs)
# Cases with no essence changes are usually ignored (NOOP). But for the not-yet-resumed objects,
# These sets are checked in few places, so we keep them centralised:
# the user-facing causes (for handlers) and internally facing (for the reactor).
HANDLER_REASONS = (
Reason.CREATE,
Reason.UPDATE,
Reason.DELETE,
Reason.RESUME,
)
REACTOR_REASONS = (
Reason.NOOP,
Reason.FREE,
Reason.GONE,
Reason.ACQUIRE,
Reason.RELEASE,
)
ALL_REASONS = HANDLER_REASONS + REACTOR_REASONS
# The human-readable names of these causes. Will be capitalised when needed.
TITLES = {
Reason.CREATE: 'creation',
Reason.UPDATE: 'update',
Reason.DELETE: 'deletion',
Reason.RESUME: 'resuming',
}
@dataclasses.dataclass
class BaseCause:
logger: Union[logging.Logger, logging.LoggerAdapter]
# Put them back to the pass-through kwargs (to avoid code duplication).
body = event['object']
kwargs.update(body=body, initial=initial)
if diff is not None:
kwargs.update(diff=diff)
# The object was really deleted from the cluster. But we do not care anymore.
if event['type'] == 'DELETED':
return ResourceChangingCause(reason=Reason.GONE, **kwargs)
# The finalizer has been just removed. We are fully done.
if finalizers.is_deleted(body) and not finalizers.has_finalizers(body):
return ResourceChangingCause(reason=Reason.FREE, **kwargs)
if finalizers.is_deleted(body):
return ResourceChangingCause(reason=Reason.DELETE, **kwargs)
# For a fresh new object, first block it from accidental deletions without our permission.
# The actual handler will be called on the next call.
# Only return this cause if the resource requires finalizers to be added.
if requires_finalizer and not finalizers.has_finalizers(body):
return ResourceChangingCause(reason=Reason.ACQUIRE, **kwargs)
# Check whether or not the resource has finalizers, but doesn't require them. If this is
# the case, then a resource may not be able to be deleted completely as finalizers may
# not be removed by the operator under normal operation. We remove the finalizers first,
# and any handler that should be called will be done on the next call.
if not requires_finalizer and finalizers.has_finalizers(body):
return ResourceChangingCause(reason=Reason.RELEASE, **kwargs)
# For an object seen for the first time (i.e. just-created), call the creation handlers,
# then mark the state as if it was seen when the creation has finished.
RELEASE = 'release'
def __str__(self) -> str:
return str(self.value)
# These sets are checked in few places, so we keep them centralised:
# the user-facing causes (for handlers) and internally facing (for the reactor).
HANDLER_REASONS = (
Reason.CREATE,
Reason.UPDATE,
Reason.DELETE,
Reason.RESUME,
)
REACTOR_REASONS = (
Reason.NOOP,
Reason.FREE,
Reason.GONE,
Reason.ACQUIRE,
Reason.RELEASE,
)
ALL_REASONS = HANDLER_REASONS + REACTOR_REASONS
# The human-readable names of these causes. Will be capitalised when needed.
TITLES = {
Reason.CREATE: 'creation',
Reason.UPDATE: 'update',
Reason.DELETE: 'deletion',
Reason.RESUME: 'resuming',
}