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) )
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) ]
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)
'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, } }] } }, {
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)
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)