Example #1
0
def app_spec():
    return AppSpec(
        name="testapp",
        namespace="default",
        image="finntech/testimage:version",
        replicas=3,
        autoscaler=AUTOSCALER_SPEC,
        resources=EMPTY_RESOURCE_SPEC,
        admin_access=False,
        secrets_in_environment=False,
        prometheus=PROMETHEUS_SPEC,
        datadog=DATADOG_SPEC,
        ports=[
            PortSpec(protocol="http", name="http", port=80, target_port=8080),
        ],
        health_checks=HealthCheckSpec(
            liveness=CheckSpec(tcp=TcpCheckSpec(port=8080), http=None, execute=None, initial_delay_seconds=10,
                               period_seconds=10, success_threshold=1, failure_threshold=3, timeout_seconds=1),
            readiness=CheckSpec(http=HttpCheckSpec(path="/", port=8080, http_headers={}), tcp=None, execute=None,
                                initial_delay_seconds=10, period_seconds=10, success_threshold=1, failure_threshold=3,
                                timeout_seconds=1)),
        teams=[u'foo'],
        tags=[u'bar'],
        deployment_id="test_app_deployment_id",
        labels=LabelAndAnnotationSpec({}, {}, {}, {}, {}),
        annotations=LabelAndAnnotationSpec({}, {}, {}, {}, {}),
        ingresses=[IngressItemSpec(host=None, pathmappings=[IngressPathMappingSpec(path="/", port=80)])],
        strongbox=StrongboxSpec(enabled=False, iam_role=None, aws_region="eu-west-1", groups=None),
        singleton=False,
        ingress_tls=IngressTlsSpec(enabled=False, certificate_issuer=None)
    )
Example #2
0
def app_spec(**kwargs):
    default_app_spec = AppSpec(
        uid="c1f34517-6f54-11ea-8eaf-0ad3d9992c8c",
        name="testapp",
        namespace="default",
        image="finntech/testimage:version",
        autoscaler=AutoscalerSpec(enabled=False, min_replicas=2, max_replicas=3, cpu_threshold_percentage=50),
        resources=ResourcesSpec(requests=ResourceRequirementSpec(cpu=None, memory=None),
                                limits=ResourceRequirementSpec(cpu=None, memory=None)),
        admin_access=False,
        secrets_in_environment=False,
        prometheus=PrometheusSpec(enabled=True, port='http', path='/internal-backstage/prometheus'),
        datadog=DatadogSpec(enabled=False, tags={}),
        ports=[
            PortSpec(protocol="http", name="http", port=80, target_port=8080),
        ],
        health_checks=HealthCheckSpec(
            liveness=CheckSpec(tcp=TcpCheckSpec(port=8080), http=None, execute=None, initial_delay_seconds=10,
                               period_seconds=10, success_threshold=1, failure_threshold=3, timeout_seconds=1),
            readiness=CheckSpec(http=HttpCheckSpec(path="/", port=8080, http_headers={}), tcp=None, execute=None,
                                initial_delay_seconds=10, period_seconds=10, success_threshold=1,
                                failure_threshold=3, timeout_seconds=1)),
        teams=[u'foo'],
        tags=[u'bar'],
        deployment_id="test_app_deployment_id",
        labels=LabelAndAnnotationSpec({}, {}, {}, {}, {}, {}),
        annotations=LabelAndAnnotationSpec({}, {}, ANNOTATIONS.copy(), {}, {}, {}),
        ingresses=[IngressItemSpec(host=None, pathmappings=[IngressPathMappingSpec(path="/", port=80)], annotations={})],
        strongbox=StrongboxSpec(enabled=False, iam_role=None, aws_region="eu-west-1", groups=None),
        singleton=False,
        ingress_tls=IngressTlsSpec(enabled=False, certificate_issuer=None),
        secrets=[]
    )

    return default_app_spec._replace(**kwargs)
 def _expand_default_hosts(self, app_spec):
     all_pathmappings = list(
         _deduplicate_in_order(
             chain.from_iterable(ingress_item.pathmappings
                                 for ingress_item in app_spec.ingresses
                                 if not ingress_item.annotations)))
     return [
         IngressItemSpec(host=host,
                         pathmappings=all_pathmappings,
                         annotations=None)
         for host in self._generate_default_hosts(app_spec.name)
     ]
Example #4
0
class TestIngressDeployer(object):
    @pytest.fixture
    def ingress_tls(self, config):
        return mock.create_autospec(IngressTls(config),
                                    spec_set=True,
                                    instance=True)

    @pytest.fixture
    def config(self):
        config = mock.create_autospec(Configuration([]), spec_set=True)
        config.ingress_suffixes = ["svc.test.example.com", "127.0.0.1.xip.io"]
        config.host_rewrite_rules = [
            HostRewriteRule("rewrite.example.com=test.rewrite.example.com"),
            HostRewriteRule(
                r"([a-z0-9](?:[-a-z0-9]*[a-z0-9])?).rewrite.example.com=test.\1.rewrite.example.com"
            )
        ]
        return config

    @pytest.fixture
    def deployer(self, config, ingress_tls):
        return IngressDeployer(config, ingress_tls)

    @pytest.fixture
    def deployer_no_suffix(self, config, ingress_tls):
        config.ingress_suffixes = []
        return IngressDeployer(config, ingress_tls)

    def pytest_generate_tests(self, metafunc):
        fixtures = ("app_spec", "expected_ingress")
        if metafunc.cls == self.__class__ and metafunc.function.__name__ == "test_ingress_deploy" and \
                all(fixname in metafunc.fixturenames for fixname in fixtures):
            for test_id, app_spec, expected_ingress in TEST_DATA:
                params = {
                    "app_spec": app_spec,
                    "expected_ingress": expected_ingress
                }
                metafunc.addcall(params, test_id)

    @pytest.mark.usefixtures("get")
    def test_ingress_deploy(self, post, deployer, app_spec, expected_ingress):
        mock_response = create_autospec(Response)
        mock_response.json.return_value = expected_ingress
        post.return_value = mock_response

        deployer.deploy(app_spec, LABELS)

        pytest.helpers.assert_any_call(post, INGRESSES_URI, expected_ingress)

    @pytest.mark.parametrize("spec_name", (
        "app_spec_thrift",
        "app_spec_no_ports",
    ))
    def test_remove_existing_ingress_if_not_needed(self, request, delete, post,
                                                   deployer, spec_name):
        app_spec = request.getfuncargvalue(spec_name)

        deployer.deploy(app_spec, LABELS)

        pytest.helpers.assert_no_calls(post, INGRESSES_URI)
        pytest.helpers.assert_any_call(delete, INGRESSES_URI + "testapp")

    @pytest.mark.usefixtures("get")
    def test_no_ingress(self, delete, post, deployer_no_suffix, app_spec):
        deployer_no_suffix.deploy(app_spec, LABELS)

        pytest.helpers.assert_no_calls(post, INGRESSES_URI)
        pytest.helpers.assert_any_call(delete, INGRESSES_URI + "testapp")

    @pytest.mark.parametrize("app_spec, hosts", (
        (app_spec(),
         [u'testapp.svc.test.example.com', u'testapp.127.0.0.1.xip.io']),
        (app_spec(ingresses=[
            IngressItemSpec(
                host="foo.rewrite.example.com",
                pathmappings=[IngressPathMappingSpec(path="/", port=80)])
        ]), [
            u'testapp.svc.test.example.com', u'testapp.127.0.0.1.xip.io',
            u'test.foo.rewrite.example.com'
        ]),
    ))
    def test_applies_ingress_tls(self, deployer, ingress_tls, app_spec, hosts):
        with mock.patch(
                "k8s.models.ingress.Ingress.get_or_create") as get_or_create:
            get_or_create.return_value = mock.create_autospec(Ingress,
                                                              spec_set=True)
            deployer.deploy(app_spec, LABELS)
            ingress_tls.apply.assert_called_once_with(IngressMatcher(),
                                                      app_spec, hosts)
Example #5
0
        'spec': {
            'rules': rules if rules else default_rules,
            'tls': tls if tls else []
        },
        'metadata': metadata if metadata else default_metadata,
    }
    return expected_ingress


TEST_DATA = (
    # (test_case_name, provided_app_spec, expected_ingress)
    ("only_default_hosts", app_spec(), ingress()),
    ("single_explicit_host",
     app_spec(ingresses=[
         IngressItemSpec(
             host="foo.example.com",
             pathmappings=[IngressPathMappingSpec(path="/", port=80)])
     ]),
     ingress(expose=True,
             rules=[{
                 'host': "foo.example.com",
                 'http': {
                     'paths': [{
                         'path': '/',
                         'backend': {
                             'serviceName': 'testapp',
                             'servicePort': 80,
                         }
                     }]
                 }
             }, {
Example #6
0
    def test_multiple_ingresses(self, post, delete, deployer, app_spec):
        app_spec.annotations.ingress.update(ANNOTATIONS.copy())
        app_spec.ingresses.append(IngressItemSpec(host="extra.example.com",
                                                  pathmappings=[IngressPathMappingSpec(path="/", port=8000)],
                                                  annotations={"some/annotation": "some-value"}))
        app_spec.ingresses.append(IngressItemSpec(host="extra.example.com",
                                                  pathmappings=[IngressPathMappingSpec(path="/_/ipblocked", port=8000)],
                                                  annotations={"some/allowlist": "10.0.0.1/12"}))

        expected_ingress = ingress()
        mock_response = create_autospec(Response)
        mock_response.json.return_value = expected_ingress

        expected_metadata2 = pytest.helpers.create_metadata('testapp-1', labels=LABELS,
                                                            annotations={"some/annotation": "some-value"}, external=True)
        expected_ingress2 = ingress(rules=[
            {
                "host": "extra.example.com",
                "http": {
                    "paths": [
                        {
                            "path": "/",
                            "backend": {
                                "serviceName": app_spec.name,
                                "servicePort": 8000
                            }
                        }
                    ]
                }
            }
        ], metadata=expected_metadata2)
        mock_response2 = create_autospec(Response)
        mock_response.json.return_value = expected_ingress2

        expected_metadata3 = pytest.helpers.create_metadata('testapp-2', labels=LABELS,
                                                            annotations={"some/annotation": "val",
                                                                         "some/allowlist": "10.0.0.1/12"}, external=True)
        expected_ingress3 = ingress(rules=[
            {
                "host": "extra.example.com",
                "http": {
                    "paths": [
                        {
                            "path": "/_/ipblocked",
                            "backend": {
                                "serviceName": app_spec.name,
                                "servicePort": 8000
                            }
                        }
                    ]
                }
            }
        ], metadata=expected_metadata3)
        mock_response3 = create_autospec(Response)
        mock_response3.json.return_value = expected_ingress3

        post.side_effect = iter([mock_response, mock_response2, mock_response3])

        deployer.deploy(app_spec, LABELS)

        post.assert_has_calls([mock.call(INGRESSES_URI, expected_ingress), mock.call(INGRESSES_URI, expected_ingress2),
                               mock.call(INGRESSES_URI, expected_ingress3)])
        delete.assert_called_once_with(INGRESSES_URI, body=None, params=LABEL_SELECTOR_PARAMS)
Example #7
0
class TestIngressDeployer(object):
    @pytest.fixture
    def ingress_tls(self, config):
        return mock.create_autospec(IngressTls(config), spec_set=True, instance=True)

    @pytest.fixture
    def config(self):
        config = mock.create_autospec(Configuration([]), spec_set=True)
        config.ingress_suffixes = ["svc.test.example.com", "127.0.0.1.xip.io"]
        config.host_rewrite_rules = [
            HostRewriteRule("rewrite.example.com=test.rewrite.example.com"),
            HostRewriteRule(r"([a-z0-9](?:[-a-z0-9]*[a-z0-9])?).rewrite.example.com=test.\1.rewrite.example.com")
        ]
        return config

    @pytest.fixture
    def deployer(self, config, ingress_tls, owner_references):
        return IngressDeployer(config, ingress_tls, owner_references)

    @pytest.fixture
    def deployer_no_suffix(self, config, ingress_tls, owner_references):
        config.ingress_suffixes = []
        return IngressDeployer(config, ingress_tls, owner_references)

    def pytest_generate_tests(self, metafunc):
        fixtures = ("app_spec", "expected_ingress")
        if metafunc.cls == self.__class__ and metafunc.function.__name__ == "test_ingress_deploy" and \
                all(fixname in metafunc.fixturenames for fixname in fixtures):
            for test_id, app_spec, expected_ingress in TEST_DATA:
                params = {"app_spec": app_spec, "expected_ingress": expected_ingress}
                metafunc.addcall(params, test_id)

    @pytest.mark.usefixtures("get")
    def test_ingress_deploy(self, post, delete, deployer, app_spec, expected_ingress, owner_references):
        mock_response = create_autospec(Response)
        mock_response.json.return_value = expected_ingress
        post.return_value = mock_response

        deployer.deploy(app_spec, LABELS)

        pytest.helpers.assert_any_call(post, INGRESSES_URI, expected_ingress)
        owner_references.apply.assert_called_once_with(TypeMatcher(Ingress), app_spec)
        delete.assert_called_once_with(INGRESSES_URI, body=None, params=LABEL_SELECTOR_PARAMS)

    @pytest.fixture
    def dtparse(self):
        with mock.patch('pyrfc3339.parse') as m:
            yield m

    @pytest.mark.usefixtures("dtparse", "get")
    def test_multiple_ingresses(self, post, delete, deployer, app_spec):
        app_spec.annotations.ingress.update(ANNOTATIONS.copy())
        app_spec.ingresses.append(IngressItemSpec(host="extra.example.com",
                                                  pathmappings=[IngressPathMappingSpec(path="/", port=8000)],
                                                  annotations={"some/annotation": "some-value"}))
        app_spec.ingresses.append(IngressItemSpec(host="extra.example.com",
                                                  pathmappings=[IngressPathMappingSpec(path="/_/ipblocked", port=8000)],
                                                  annotations={"some/allowlist": "10.0.0.1/12"}))

        expected_ingress = ingress()
        mock_response = create_autospec(Response)
        mock_response.json.return_value = expected_ingress

        expected_metadata2 = pytest.helpers.create_metadata('testapp-1', labels=LABELS,
                                                            annotations={"some/annotation": "some-value"}, external=True)
        expected_ingress2 = ingress(rules=[
            {
                "host": "extra.example.com",
                "http": {
                    "paths": [
                        {
                            "path": "/",
                            "backend": {
                                "serviceName": app_spec.name,
                                "servicePort": 8000
                            }
                        }
                    ]
                }
            }
        ], metadata=expected_metadata2)
        mock_response2 = create_autospec(Response)
        mock_response.json.return_value = expected_ingress2

        expected_metadata3 = pytest.helpers.create_metadata('testapp-2', labels=LABELS,
                                                            annotations={"some/annotation": "val",
                                                                         "some/allowlist": "10.0.0.1/12"}, external=True)
        expected_ingress3 = ingress(rules=[
            {
                "host": "extra.example.com",
                "http": {
                    "paths": [
                        {
                            "path": "/_/ipblocked",
                            "backend": {
                                "serviceName": app_spec.name,
                                "servicePort": 8000
                            }
                        }
                    ]
                }
            }
        ], metadata=expected_metadata3)
        mock_response3 = create_autospec(Response)
        mock_response3.json.return_value = expected_ingress3

        post.side_effect = iter([mock_response, mock_response2, mock_response3])

        deployer.deploy(app_spec, LABELS)

        post.assert_has_calls([mock.call(INGRESSES_URI, expected_ingress), mock.call(INGRESSES_URI, expected_ingress2),
                               mock.call(INGRESSES_URI, expected_ingress3)])
        delete.assert_called_once_with(INGRESSES_URI, body=None, params=LABEL_SELECTOR_PARAMS)

    @pytest.mark.parametrize("spec_name", (
            "app_spec_thrift",
            "app_spec_no_ports",
    ))
    def test_remove_existing_ingress_if_not_needed(self, request, delete, post, deployer, spec_name):
        app_spec = request.getfuncargvalue(spec_name)

        deployer.deploy(app_spec, LABELS)

        pytest.helpers.assert_no_calls(post, INGRESSES_URI)
        pytest.helpers.assert_any_call(delete, INGRESSES_URI, body=None, params=LABEL_SELECTOR_PARAMS)

    @pytest.mark.usefixtures("get")
    def test_no_ingress(self, delete, post, deployer_no_suffix, app_spec):
        deployer_no_suffix.deploy(app_spec, LABELS)

        pytest.helpers.assert_no_calls(post, INGRESSES_URI)
        pytest.helpers.assert_any_call(delete, INGRESSES_URI, body=None, params=LABEL_SELECTOR_PARAMS)

    @pytest.mark.parametrize("app_spec, hosts", (
            (app_spec(), [u'testapp.svc.test.example.com', u'testapp.127.0.0.1.xip.io']),
            (app_spec(ingresses=[
                IngressItemSpec(host="foo.rewrite.example.com",
                                pathmappings=[IngressPathMappingSpec(path="/", port=80)], annotations={})]),
             [u'test.foo.rewrite.example.com', u'testapp.svc.test.example.com', u'testapp.127.0.0.1.xip.io']),
    ))
    @pytest.mark.usefixtures("delete")
    def test_applies_ingress_tls(self, deployer, ingress_tls, app_spec, hosts):
        with mock.patch("k8s.models.ingress.Ingress.get_or_create") as get_or_create:
            get_or_create.return_value = mock.create_autospec(Ingress, spec_set=True)
            deployer.deploy(app_spec, LABELS)
            ingress_tls.apply.assert_called_once_with(TypeMatcher(Ingress), app_spec, hosts, use_suffixes=True)
    def test_applies_ingress_tls_issuser_overrides(self, post,
                                                   deployer_issuer_overrides,
                                                   ingress_tls, app_spec):
        with mock.patch(
                "k8s.models.ingress.Ingress.get_or_create") as get_or_create:
            get_or_create.return_value = mock.create_autospec(Ingress,
                                                              spec_set=True)
            app_spec.ingresses[:] = [
                # has issuer-override
                IngressItemSpec(
                    host="foo.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={}),
                # no issuer-override
                IngressItemSpec(
                    host="bar.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={}),
                IngressItemSpec(
                    host="foo.bar.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={}),
                IngressItemSpec(
                    host="other.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={}),
                # suffix has issuer-override
                IngressItemSpec(
                    host="sub.foo.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={}),
                # more specific suffix has issuer-override
                IngressItemSpec(
                    host="sub.bar.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={}),
                # has annotations
                IngressItemSpec(
                    host="ann.foo.example.com",
                    pathmappings=[IngressPathMappingSpec(path="/", port=80)],
                    annotations={"some": "annotation"})
            ]

            deployer_issuer_overrides.deploy(app_spec, LABELS)
            host_groups = [
                sorted(call.args[2])
                for call in ingress_tls.apply.call_args_list
            ]
            expected_host_groups = [["ann.foo.example.com"],
                                    [
                                        "bar.example.com", "other.example.com",
                                        "sub.bar.example.com",
                                        "testapp.127.0.0.1.xip.io",
                                        "testapp.svc.test.example.com"
                                    ],
                                    [
                                        "foo.bar.example.com",
                                        "foo.example.com",
                                        "sub.foo.example.com"
                                    ]]
            assert ingress_tls.apply.call_count == 3
            assert expected_host_groups == sorted(host_groups)