def test_removal_identifies_by_uid():
    owner1 = copy.deepcopy(OWNER)
    owner2 = copy.deepcopy(OWNER)
    owner3 = copy.deepcopy(OWNER)
    owner1['kind'] = 'KindA'
    owner2['kind'] = 'KindA'
    owner3['kind'] = 'KindB'
    owner1['metadata']['name'] = 'name-a'
    owner2['metadata']['name'] = 'name-b'
    owner3['metadata']['name'] = 'name-b'
    owner1['metadata']['uid'] = 'uid-0'
    owner2['metadata']['uid'] = 'uid-0'
    owner3['metadata']['uid'] = 'uid-0'
    obj = {}

    # Three different owners added, but all have the same uid.
    # One is removed and only once, all must be gone (due to same uids).
    kopf.append_owner_reference(
        obj, owner=Body(owner1))  # assumed to work, tested above
    kopf.append_owner_reference(
        obj, owner=Body(owner2))  # assumed to work, tested above
    kopf.append_owner_reference(
        obj, owner=Body(owner3))  # assumed to work, tested above
    kopf.remove_owner_reference(
        obj, owner=Body(owner1))  # this one is being tested here

    assert len(obj['metadata']['ownerReferences']) == 0
def test_set_awake_time(storage, handler, expected, body, delay):
    patch = Patch()
    state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    state = state.with_handlers([handler])
    state = state.with_outcomes(outcomes={handler.id: Outcome(final=False, delay=delay)})
    state.store(patch=patch, body=Body(body), storage=storage)
    assert patch['status']['kopf']['progress']['some-id'].get('delayed') == expected
def test_adopting(mocker, forced, strict, nested):
    # These methods are tested in their own tests.
    # We just check that they are called at all.
    append_owner_ref = mocker.patch(
        'kopf._kits.hierarchies.append_owner_reference')
    harmonize_naming = mocker.patch('kopf._kits.hierarchies.harmonize_naming')
    adjust_namespace = mocker.patch('kopf._kits.hierarchies.adjust_namespace')
    label = mocker.patch('kopf._kits.hierarchies.label')

    obj = {}
    kopf.adopt(obj,
               owner=Body(OWNER),
               forced=forced,
               strict=strict,
               nested=nested)

    assert append_owner_ref.called
    assert harmonize_naming.called
    assert adjust_namespace.called
    assert label.called

    assert append_owner_ref.call_args == call(obj, owner=Body(OWNER))
    assert harmonize_naming.call_args == call(obj,
                                              name=OWNER_NAME,
                                              forced=forced,
                                              strict=strict)
    assert adjust_namespace.call_args == call(obj,
                                              namespace=OWNER_NAMESPACE,
                                              forced=forced)
    assert label.call_args == call(obj,
                                   labels=OWNER_LABELS,
                                   nested=nested,
                                   forced=forced)
def test_appending_deduplicates_by_uid():
    """
    The uid is the only necessary criterion to identify same objects.
    No matter how we change the irrelevant non-id fields, they must be ignored.
    """
    owner1 = copy.deepcopy(OWNER)
    owner2 = copy.deepcopy(OWNER)
    owner3 = copy.deepcopy(OWNER)
    owner1['kind'] = 'KindA'
    owner2['kind'] = 'KindA'
    owner3['kind'] = 'KindB'
    owner1['metadata']['name'] = 'name-a'
    owner2['metadata']['name'] = 'name-b'
    owner3['metadata']['name'] = 'name-b'
    owner1['metadata']['uid'] = 'uid-0'
    owner2['metadata']['uid'] = 'uid-0'
    owner3['metadata']['uid'] = 'uid-0'
    obj = {}

    kopf.append_owner_reference(obj, owner=Body(owner1))
    kopf.append_owner_reference(obj, owner=Body(owner2))
    kopf.append_owner_reference(obj, owner=Body(owner3))

    assert len(obj['metadata']['ownerReferences']) == 1
    assert obj['metadata']['ownerReferences'][0]['uid'] == 'uid-0'
def test_started_from_storage_is_preferred_over_from_scratch(storage, handler, body, expected):
    with freezegun.freeze_time(TS0):
        state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    with freezegun.freeze_time(TS1):
        state = state.with_handlers([handler])
    patch = Patch()
    state.store(body=Body({}), patch=patch, storage=storage)
    assert patch['status']['kopf']['progress']['some-id']['started'] == expected
def test_removal_from_kubernetes_model(kubernetes_model):
    kubernetes_model.metadata = None
    kopf.append_owner_reference(kubernetes_model, owner=Body(OWNER))
    kopf.remove_owner_reference(kubernetes_model, owner=Body(OWNER))
    assert kubernetes_model.metadata is not None
    assert kubernetes_model.metadata.owner_references is not None
    assert isinstance(kubernetes_model.metadata.owner_references, list)
    assert len(kubernetes_model.metadata.owner_references) == 0
def test_removal_from_pykube_object(pykube_object):
    del pykube_object.obj['metadata']
    kopf.append_owner_reference(pykube_object, owner=Body(OWNER))
    kopf.remove_owner_reference(pykube_object, owner=Body(OWNER))
    assert 'metadata' in pykube_object.obj
    assert 'ownerReferences' in pykube_object.obj['metadata']
    assert isinstance(pykube_object.obj['metadata']['ownerReferences'], list)
    assert len(pykube_object.obj['metadata']['ownerReferences']) == 0
def test_subrefs_ignored_when_not_specified(storage, handler):
    body = {}
    patch = Patch()
    outcome = Outcome(final=True, subrefs=[])
    state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    state = state.with_handlers([handler])
    state = state.with_outcomes(outcomes={handler.id: outcome})
    state.store(patch=patch, body=Body(body), storage=storage)
    assert patch['status']['kopf']['progress']['some-id']['subrefs'] is None
def test_subrefs_added_to_preexisting_subrefs(storage, handler):
    body = {'status': {'kopf': {'progress': {'some-id': {'subrefs': ['sub9/2', 'sub9/1']}}}}}
    patch = Patch()
    outcome_subrefs = ['sub2/b', 'sub2/a', 'sub2', 'sub1', 'sub3']
    expected_subrefs = ['sub1', 'sub2', 'sub2/a', 'sub2/b', 'sub3', 'sub9/1', 'sub9/2']
    outcome = Outcome(final=True, subrefs=outcome_subrefs)
    state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    state = state.with_handlers([handler])
    state = state.with_outcomes(outcomes={handler.id: outcome})
    state.store(patch=patch, body=Body(body), storage=storage)
    assert patch['status']['kopf']['progress']['some-id']['subrefs'] == expected_subrefs
Exemple #10
0
def test_store_success(storage, handler, expected_retries, expected_stopped, body):
    patch = Patch()
    state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    state = state.with_handlers([handler])
    state = state.with_outcomes(outcomes={handler.id: Outcome(final=True)})
    state.store(patch=patch, body=Body(body), storage=storage)
    assert patch['status']['kopf']['progress']['some-id']['success'] is True
    assert patch['status']['kopf']['progress']['some-id']['failure'] is False
    assert patch['status']['kopf']['progress']['some-id']['retries'] == expected_retries
    assert patch['status']['kopf']['progress']['some-id']['stopped'] == expected_stopped
    assert patch['status']['kopf']['progress']['some-id']['message'] is None
Exemple #11
0
 def __init__(self, *, body: bodies.Body, settings: configuration.OperatorSettings) -> None:
     super().__init__(logger, dict(
         settings=settings,
         k8s_skip=False,
         k8s_ref=dict(
             apiVersion=body.get('apiVersion'),
             kind=body.get('kind'),
             name=body.get('metadata', {}).get('name'),
             uid=body.get('metadata', {}).get('uid'),
             namespace=body.get('metadata', {}).get('namespace'),
         ),
     ))
def test_removal_from_dict():
    obj = {}

    kopf.append_owner_reference(
        obj, owner=Body(OWNER))  # assumed to work, tested above
    kopf.remove_owner_reference(
        obj, owner=Body(OWNER))  # this one is being tested here

    assert 'metadata' in obj
    assert 'ownerReferences' in obj['metadata']
    assert isinstance(obj['metadata']['ownerReferences'], list)
    assert len(obj['metadata']['ownerReferences']) == 0
def test_touching_via_status_storage_with_none_when_absent(cls):
    storage = cls(touch_field='status.my-dummy')
    patch = Patch()
    body = Body({})
    storage.touch(body=body, patch=patch, value=None)

    assert not patch
Exemple #14
0
async def test_patching_without_inconsistencies(resource, namespace, settings,
                                                caplog, assert_logs,
                                                version_api, aresponses,
                                                hostname, resp_mocker, patch,
                                                response):
    caplog.set_level(logging.DEBUG)

    url = resource.get_url(namespace=namespace, name='name1')
    patch_mock = resp_mocker(return_value=aiohttp.web.json_response(response))
    aresponses.add(hostname, url, 'patch', patch_mock)

    body = Body({'metadata': {'namespace': namespace, 'name': 'name1'}})
    logger = LocalObjectLogger(body=body, settings=settings)
    await patch_and_check(
        settings=settings,
        resource=resource,
        body=body,
        patch=Patch(patch),
        logger=logger,
    )

    assert_logs([
        "Patching with:",
    ],
                prohibited=[
                    "Patching failed with inconsistencies:",
                ])
def test_appending_distinguishes_by_uid():
    """
    Changing only the uid should be sufficient to consider a new owner.
    Here, all other non-id fields are the same, and must be ignored.
    """
    owner1 = copy.deepcopy(OWNER)
    owner2 = copy.deepcopy(OWNER)
    owner1['metadata']['uid'] = 'uid-a'
    owner2['metadata']['uid'] = 'uid-b'
    obj = {}

    kopf.append_owner_reference(obj, owner=Body(owner1))
    kopf.append_owner_reference(obj, owner=Body(owner2))

    uids = {ref['uid'] for ref in obj['metadata']['ownerReferences']}
    assert uids == {'uid-a', 'uid-b'}
Exemple #16
0
def test_issue_601_deletion_supersedes_other_processing(storage, reason):

    body = {'status': {'kopf': {'progress': {
        'fn1': {'purpose': reason.value, 'failure': True},
        'fn2': {'purpose': reason.value, 'success': True},
        'fn3': {'purpose': reason.value, 'delayed': TS1_ISO},
    }}}}
    create_handler1 = Mock(id='fn1', spec_set=['id'])
    create_handler2 = Mock(id='fn2', spec_set=['id'])
    create_handler3 = Mock(id='fn3', spec_set=['id'])
    delete_handler9 = Mock(id='delete_fn', spec_set=['id'])
    owned_handlers = [create_handler1, create_handler2, create_handler3, delete_handler9]
    cause_handlers = [delete_handler9]

    state = State.from_storage(body=Body(body), handlers=owned_handlers, storage=storage)
    state = state.with_purpose(Reason.DELETE)
    state = state.with_handlers(cause_handlers)

    assert len(state) == 4
    assert state.extras == {reason: StateCounters(success=1, failure=1, running=1)}
    assert state.counts == StateCounters(success=0, failure=0, running=1)
    assert state.done == False
    assert state.delays == [0.0]

    state = state.with_outcomes({'delete_fn': Outcome(final=True)})

    assert state.extras == {reason: StateCounters(success=1, failure=1, running=1)}
    assert state.counts == StateCounters(success=1, failure=0, running=0)
    assert state.done == True
    assert state.delays == []
def test_touching_via_annotations_storage_with_none_when_absent(cls):
    storage = cls(prefix='my-operator.example.com', touch_key='my-dummy')
    patch = Patch()
    body = Body({})
    storage.touch(body=body, patch=patch, value=None)

    assert not patch
Exemple #18
0
def test_daemon_kwargs(resource, attr):
    body = {'metadata': {'uid': 'uid1', 'name': 'name1', 'namespace': 'ns1',
                         'labels': {'l1': 'v1'}, 'annotations': {'a1': 'v1'}},
            'spec': {'field': 'value'},
            'status': {'info': 'payload'}}
    cause = DaemonCause(
        logger=logging.getLogger('kopf.test.fake.logger'),
        indices=OperatorIndexers().indices,
        resource=resource,
        patch=Patch(),
        memo=Memo(),
        body=Body(body),
        stopper=DaemonStopper(),
    )
    kwargs = getattr(cause, attr)  # cause.kwargs
    assert set(kwargs) == {'logger', 'resource', 'patch', 'memo',
                           'body', 'spec', 'status', 'meta', 'uid', 'name', 'namespace',
                           'labels', 'annotations'}
    assert kwargs['resource'] is cause.resource
    assert kwargs['logger'] is cause.logger
    assert kwargs['patch'] is cause.patch
    assert kwargs['memo'] is cause.memo
    assert kwargs['body'] is cause.body
    assert kwargs['spec'] is cause.body.spec
    assert kwargs['meta'] is cause.body.metadata
    assert kwargs['status'] is cause.body.status
    assert kwargs['labels'] is cause.body.metadata.labels
    assert kwargs['annotations'] is cause.body.metadata.annotations
    assert kwargs['uid'] == cause.body.metadata.uid
    assert kwargs['name'] == cause.body.metadata.name
    assert kwargs['namespace'] == cause.body.metadata.namespace
    assert 'stopper' not in kwargs
    assert 'stopped' not in kwargs
Exemple #19
0
def test_repurposed_not_affecting_the_existing_handlers_from_storage(storage, handler, reason):
    body = {'status': {'kopf': {'progress': {'some-id': {'purpose': None}}}}}
    state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    state = state.with_handlers([handler]).with_purpose(reason).with_handlers([handler])
    assert len(state) == 1
    assert state.purpose == reason
    assert state['some-id'].purpose is None
def owner(request, resource):
    body = Body(copy.deepcopy(OWNER))
    if request.param == 'state-changing-cause':
        cause = ChangingCause(
            logger=logging.getLogger('kopf.test.fake.logger'),
            indices=OperatorIndexers().indices,
            resource=resource,
            patch=Patch(),
            memo=Memo(),
            body=body,
            initial=False,
            reason=Reason.NOOP,
        )
        with context([(cause_var, cause)]):
            yield body
    elif request.param == 'event-watching-cause':
        cause = WatchingCause(
            logger=logging.getLogger('kopf.test.fake.logger'),
            indices=OperatorIndexers().indices,
            resource=resource,
            patch=Patch(),
            memo=Memo(),
            body=body,
            type='irrelevant',
            event=RawEvent(type='irrelevant', object=OWNER),
        )
        with context([(cause_var, cause)]):
            yield body
    else:
        raise RuntimeError(f"Wrong param for `owner` fixture: {request.param!r}")
Exemple #21
0
def test_watching_kwargs(resource, attr):
    body = {'metadata': {'uid': 'uid1', 'name': 'name1', 'namespace': 'ns1',
                         'labels': {'l1': 'v1'}, 'annotations': {'a1': 'v1'}},
            'spec': {'field': 'value'},
            'status': {'info': 'payload'}}
    cause = WatchingCause(
        logger=logging.getLogger('kopf.test.fake.logger'),
        indices=OperatorIndexers().indices,
        resource=resource,
        patch=Patch(),
        memo=Memo(),
        body=Body(body),
        type='ADDED',
        event={'type': 'ADDED', 'object': {}},
    )
    kwargs = getattr(cause, attr)  # cause.kwargs / cause.sync_kwargs / cause.async_kwargs
    assert set(kwargs) == {'logger', 'resource',
                           'patch', 'event', 'type', 'memo',
                           'body', 'spec', 'status', 'meta', 'uid', 'name', 'namespace',
                           'labels', 'annotations'}
    assert kwargs['resource'] is cause.resource
    assert kwargs['logger'] is cause.logger
    assert kwargs['patch'] is cause.patch
    assert kwargs['event'] is cause.event
    assert kwargs['memo'] is cause.memo
    assert kwargs['type'] is cause.type
    assert kwargs['body'] is cause.body
    assert kwargs['spec'] is cause.body.spec
    assert kwargs['meta'] is cause.body.metadata
    assert kwargs['status'] is cause.body.status
    assert kwargs['labels'] is cause.body.metadata.labels
    assert kwargs['annotations'] is cause.body.metadata.annotations
    assert kwargs['uid'] == cause.body.metadata.uid
    assert kwargs['name'] == cause.body.metadata.name
    assert kwargs['namespace'] == cause.body.metadata.namespace
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 = ChangingCause(
        logger=logging.getLogger('kopf.test.fake.logger'),
        indices=OperatorIndexers().indices,
        resource=resource,
        patch=Patch(),
        initial=False,
        reason=Reason.NOOP,
        memo=object(),
        body=Body(body),
        diff=object(),
        old=object(),
        new=object(),
    )

    fn = MagicMock(fn)
    await invoke(fn, kwargsrc=cause)

    assert fn.called
    assert fn.call_count == 1

    # Only check that kwargs are passed at all. The exact kwargs per cause are tested separately.
    assert 'logger' in fn.call_args[1]
    assert 'resource' in fn.call_args[1]
Exemple #23
0
async def test_patching_with_disappearance(
        resource, namespace, settings, caplog, assert_logs, version_api,
        aresponses, hostname, resp_mocker):
    caplog.set_level(logging.DEBUG)

    patch = {'spec': {'x': 'y'}, 'status': {'s': 't'}}  # irrelevant
    url = resource.get_url(namespace=namespace, name='name1')
    patch_mock = resp_mocker(return_value=aresponses.Response(status=404))
    aresponses.add(hostname, url, 'patch', patch_mock)

    body = Body({'metadata': {'namespace': namespace, 'name': 'name1'}})
    logger = LocalObjectLogger(body=body, settings=settings)
    await patch_and_check(
        resource=resource,
        body=body,
        patch=Patch(patch),
        logger=logger,
    )

    assert_logs([
        "Patching with:",
        "Patching was skipped: the object does not exist anymore",
    ], prohibited=[
        "inconsistencies"
    ])
Exemple #24
0
def test_purge_progress_cascades_to_subrefs(storage, handler):
    body = {'status': {'kopf': {'progress': {
        'some-id': {'subrefs': ['sub1', 'sub2', 'sub3']},
        'sub1': {},
        'sub2': {},
        # 'sub3' is intentionally absent -- should not be purged as already non-existent.
        'sub-unrelated': {},  # should be ignored, as not related to the involved handlers.
    }}}}
    patch = Patch()
    state = State.from_storage(body=Body(body), handlers=[handler], storage=storage)
    state.purge(patch=patch, body=Body(body), storage=storage, handlers=[handler])
    assert patch == {'status': {'kopf': {'progress': {
        'some-id': None,
        'sub1': None,
        'sub2': None,
    }}}}
def test_touching_via_status_storage_with_none_when_present(cls):
    storage = cls(touch_field='status.my-dummy')
    patch = Patch()
    body = Body({'status': {'my-dummy': 'something'}})
    storage.touch(body=body, patch=patch, value=None)

    assert patch
    assert patch['status']['my-dummy'] is None
Exemple #26
0
 def mark_key(self, key: str, *, body: bodies.Body) -> str:
     owners = body.meta.get('ownerReferences', [])
     kind = body.get('kind')
     if kind == 'ReplicaSet' and any(owner['kind'] == 'Deployment'
                                     for owner in owners):
         return f"{key}-ofDRS"  # no need to generalise for a single known case
     else:
         return key
Exemple #27
0
def test_get_essence_removes_system_fields_and_cleans_parents(
    field: str,
    cls: Type[DiffBaseStorage],
):
    body = Body({'metadata': {field: 'x'}})
    storage = cls()
    essence = storage.build(body=body)
    assert essence == {}
Exemple #28
0
def test_get_essence_removes_system_fields_but_keeps_extra_fields(
    field: str,
    cls: Type[DiffBaseStorage],
):
    body = Body({'metadata': {field: 'x', 'other': 'y'}})
    storage = cls()
    essence = storage.build(body=body, extra_fields=['metadata.other'])
    assert essence == {'metadata': {'other': 'y'}}
Exemple #29
0
def test_get_essence_removes_garbage_annotations_and_cleans_parents(
    annotation: str,
    cls: Type[DiffBaseStorage],
):
    body = Body({'metadata': {'annotations': {annotation: 'x'}}})
    storage = cls()
    essence = storage.build(body=body)
    assert essence == {}
Exemple #30
0
def test_get_essence_removes_garbage_annotations_but_keeps_others(
    annotation: str,
    cls: Type[DiffBaseStorage],
):
    body = Body({'metadata': {'annotations': {annotation: 'x', 'other': 'y'}}})
    storage = cls()
    essence = storage.build(body=body)
    assert essence == {'metadata': {'annotations': {'other': 'y'}}}