Example #1
0
async def test_no_peering_tasks_with_no_peering_resources(settings, ensemble):

    settings.peering.mandatory = False
    insights = Insights()
    r1 = Resource(group='group1',
                  version='version1',
                  plural='plural1',
                  namespaced=True)
    insights.resources.add(r1)
    insights.namespaces.add('ns1')

    await adjust_tasks(
        processor=processor,
        identity=Identity('...'),
        settings=settings,
        insights=insights,
        ensemble=ensemble,
    )

    assert ensemble.watcher_tasks
    assert not ensemble.peering_tasks
    assert not ensemble.pinging_tasks
    assert not ensemble.freeze_toggles
Example #2
0
async def test_followups_for_deletion_of_resource(registry, apis_mock,
                                                  group1_empty_mock, timer,
                                                  etype):
    e1 = RawEvent(type=etype, object=RawBody(spec={'group': 'group1'}))
    r1 = Resource(group='group1', version='version1', plural='plural1')
    insights = Insights()
    insights.resources.add(r1)

    async def delayed_injection(delay: float):
        await asyncio.sleep(delay)
        await process_discovered_resource_event(insights=insights,
                                                raw_event=e1,
                                                registry=registry)

    task = asyncio.create_task(delayed_injection(0.1))
    async with timer, async_timeout.timeout(1.0):
        async with insights.revised:
            await insights.revised.wait()
    await task
    assert 0.1 < timer.seconds < 1.0
    assert not insights.resources
    assert apis_mock.called
    assert group1_empty_mock.called
Example #3
0
def test_on_create_minimal(cause_factory):
    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.CREATE)

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

    handlers = registry.resource_changing_handlers.get_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].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].backoff is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
    assert handlers[0].field is None
    assert handlers[0].old is None
    assert handlers[0].new is None
Example #4
0
def test_on_create_minimal(cause_factory):

    registry = kopf.get_default_registry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=Reason.CREATE)

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

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        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].errors is None
    assert handlers[0].timeout is None
    assert handlers[0].retries is None
    assert handlers[0].backoff is None
    assert handlers[0].labels is None
    assert handlers[0].annotations is None
    assert handlers[0].when is None
Example #5
0
def test_on_field_with_cooldown(mocker, cause_factory):
    registry = kopf.get_default_registry()
    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)

    with pytest.deprecated_call(match=r"use backoff="):

        @kopf.on.field('group',
                       'version',
                       'plural',
                       field='field.subfield',
                       cooldown=78)
        def fn(**_):
            pass

    handlers = registry.resource_changing_handlers.get_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].backoff == 78

    with pytest.deprecated_call(match=r"use handler.backoff"):
        assert handlers[0].cooldown == 78
Example #6
0
def test_url_for_a_list_of_namespaced_custom_resources_clusterwide():
    resource = Resource('group', 'version', 'plural', namespaced=True)
    url = resource.get_url(namespace=None)
    assert url == '/apis/group/version/plural'
Example #7
0
def test_name_of_corev1_resource():
    resource = Resource('', 'v1', 'plural')
    name = resource.name
    assert name == 'plural'
Example #8
0
def resource():
    """ The resource used in the tests. Usually mocked, so it does not matter. """
    return Resource('zalando.org', 'v1', 'kopfexamples')
Example #9
0
def namespaced_peering_resource(request):
    return Resource(*request.param[:3], namespaced=True)
Example #10
0
def cluster_resource():
    """ The resource used in the tests. Usually mocked, so it does not matter. """
    return Resource('kopf.dev', 'v1', 'kopfexamples', namespaced=False)
Example #11
0
def cluster_peering_resource(request):
    return Resource(*request.param[:3], namespaced=False)
Example #12
0
                                    NAMESPACES, Backbone, Resource, Selector


@pytest.mark.parametrize('selector', [
    CRDS, EVENTS, NAMESPACES, CLUSTER_PEERINGS, NAMESPACED_PEERINGS,
])
def test_empty_backbone(selector: Selector):
    backbone = Backbone()
    assert len(backbone) == 0
    assert set(backbone) == set()
    with pytest.raises(KeyError):
        assert backbone[selector]


@pytest.mark.parametrize('selector, resource', [
    (CRDS, Resource('apiextensions.k8s.io', 'v1beta1', 'customresourcedefinitions')),
    (CRDS, Resource('apiextensions.k8s.io', 'v1', 'customresourcedefinitions')),
    (CRDS, Resource('apiextensions.k8s.io', 'vX', 'customresourcedefinitions')),
    (EVENTS, Resource('', 'v1', 'events')),
    (NAMESPACES, Resource('', 'v1', 'namespaces')),
    (CLUSTER_PEERINGS, Resource('kopf.dev', 'v1', 'clusterkopfpeerings')),
    (NAMESPACED_PEERINGS, Resource('kopf.dev', 'v1', 'kopfpeerings')),
    (CLUSTER_PEERINGS, Resource('zalando.org', 'v1', 'clusterkopfpeerings')),
    (NAMESPACED_PEERINGS, Resource('zalando.org', 'v1', 'kopfpeerings')),
])
async def test_refill_populates_the_resources(selector: Selector, resource: Resource):
    backbone = Backbone()
    await backbone.fill(resources=[resource])
    assert len(backbone) == 1
    assert set(backbone) == {selector}
    assert backbone[selector] == resource
Example #13
0
def test_url_for_a_specific_clusterscoped_corev1_subresource_clusterwide():
    resource = Resource('', 'v1', 'plural', namespaced=False)
    url = resource.get_url(namespace=None,
                           name='name-a.b',
                           subresource='status')
    assert url == '/api/v1/plural/name-a.b/status'
Example #14
0
def test_url_for_a_specific_namespaced_custom_subresource_in_a_namespace():
    resource = Resource('group', 'version', 'plural', namespaced=True)
    url = resource.get_url(namespace='ns-a.b',
                           name='name-a.b',
                           subresource='status')
    assert url == '/apis/group/version/namespaces/ns-a.b/plural/name-a.b/status'
Example #15
0
def test_url_with_arbitrary_params():
    resource = Resource('group', 'version', 'plural')
    url = resource.get_url(
        params=dict(watch='true', resourceVersion='abc%def xyz'))
    assert url == '/apis/group/version/plural?watch=true&resourceVersion=abc%25def+xyz'
Example #16
0
def test_url_for_a_list_of_namespaced_corev1_resources_in_a_namespace():
    resource = Resource('', 'v1', 'plural', namespaced=True)
    url = resource.get_url(namespace='ns-a.b')
    assert url == '/api/v1/namespaces/ns-a.b/plural'
Example #17
0
def test_url_for_a_list_of_namespaced_corev1_resources_clusterwide():
    resource = Resource('', 'v1', 'plural', namespaced=True)
    url = resource.get_url(namespace=None)
    assert url == '/api/v1/plural'
Example #18
0
def test_different_resource():
    selector = Selector('group1', 'version1', 'plural1')
    resource = Resource('group2', 'version2', 'plural2')
    handler = Mock(selector=selector)
    matches = _matches_resource(handler, resource)
    assert not matches
Example #19
0
def test_url_for_a_list_of_namespaced_custom_resources_in_a_namespace():
    resource = Resource('group', 'version', 'plural', namespaced=True)
    url = resource.get_url(namespace='ns-a.b')
    assert url == '/apis/group/version/namespaces/ns-a.b/plural'
Example #20
0
def test_url_for_a_specific_namespaced_corev1_subresource_in_a_namespace():
    resource = Resource('', 'v1', 'plural', namespaced=True)
    url = resource.get_url(namespace='ns-a.b',
                           name='name-a.b',
                           subresource='status')
    assert url == '/api/v1/namespaces/ns-a.b/plural/name-a.b/status'
Example #21
0
def test_url_for_a_specific_clusterscoped_custom_resource_clusterwide():
    resource = Resource('group', 'version', 'plural', namespaced=False)
    url = resource.get_url(namespace=None, name='name-a.b')
    assert url == '/apis/group/version/plural/name-a.b'
Example #22
0
def test_api_version_of_custom_resource():
    resource = Resource('group', 'version', 'plural')
    api_version = resource.api_version
    assert api_version == 'group/version'
Example #23
0
async def test_refill_is_cumulative_ie_does_not_reset():
    backbone = Backbone()
    await backbone.fill(resources=[Resource('', 'v1', 'namespaces')])
    await backbone.fill(resources=[Resource('', 'v1', 'events')])
    assert len(backbone) == 2
    assert set(backbone) == {NAMESPACES, EVENTS}
Example #24
0
def test_catchall_with_none():
    resource = Resource('group2', 'version2', 'plural2')
    handler = Mock(selector=None)
    matches = _matches_resource(handler, resource)
    assert matches
Example #25
0
def peering_resource(request):
    return Resource(*request.param[:3], namespaced=request.param[3])
Example #26
0
def test_name_of_custom_resource():
    resource = Resource('group', 'version', 'plural')
    name = resource.name
    assert name == 'plural.group'
Example #27
0
def resource(request):
    """ The resource used in the tests. Usually mocked, so it does not matter. """
    return Resource('kopf.dev', 'v1', 'kopfexamples', namespaced=request.param)
Example #28
0
def test_creation_with_no_args():
    with pytest.raises(TypeError):
        Resource()
Example #29
0
def test_api_version_of_corev1_resource():
    resource = Resource('', 'v1', 'plural')
    api_version = resource.api_version
    assert api_version == 'v1'
Example #30
0
async def patch_obj(
        *,
        resource: references.Resource,
        namespace: references.Namespace,
        name: Optional[str],
        patch: patches.Patch,
        context: Optional[auth.APIContext] = None,  # injected by the decorator
) -> Optional[bodies.RawBody]:
    """
    Patch a resource of specific kind.

    Unlike the object listing, the namespaced call is always
    used for the namespaced resources, even if the operator serves
    the whole cluster (i.e. is not namespace-restricted).

    Returns the patched body. The patched body can be partial (status-only,
    no-status, or empty) -- depending on whether there were fields in the body
    or in the status to patch; if neither had fields for patching, the result
    is an empty body. The result should only be used to check against the patch:
    if there was nothing to patch, it does not matter if the fields are absent.

    Returns ``None`` if the underlying object is absent, as detected by trying
    to patch it and failing with HTTP 404. This can happen if the object was
    deleted in the operator's handlers or externally during the processing,
    so that the framework was unaware of these changes until the last moment.
    """
    if context is None:
        raise RuntimeError("API instance is not injected by the decorator.")

    as_subresource = 'status' in resource.subresources
    body_patch = dict(
        patch)  # shallow: for mutation of the top-level keys below.
    status_patch = body_patch.pop('status', None) if as_subresource else None

    # Patch & reconstruct the actual body as reported by the server. The reconstructed body can be
    # partial or empty -- if the body/status patches are empty. This is fine: it is only used
    # to verify that the patched fields are matching the patch. No patch? No mismatch!
    try:
        patched_body = bodies.RawBody()

        if body_patch:
            response = await context.session.patch(
                url=resource.get_url(server=context.server,
                                     namespace=namespace,
                                     name=name),
                headers={'Content-Type': 'application/merge-patch+json'},
                json=body_patch,
            )
            patched_body = await errors.parse_response(response)

        if status_patch:
            response = await context.session.patch(
                url=resource.get_url(
                    server=context.server,
                    namespace=namespace,
                    name=name,
                    subresource='status' if as_subresource else None),
                headers={'Content-Type': 'application/merge-patch+json'},
                json={'status': status_patch},
            )
            patched_body['status'] = (
                await errors.parse_response(response)).get('status')

        return patched_body

    except errors.APINotFoundError:
        return None