...

Text file src/github.com/emissary-ingress/emissary/v3/python/tests/kat/t_basics.py

Documentation: github.com/emissary-ingress/emissary/v3/python/tests/kat

     1import re
     2from typing import Generator, Tuple, Union
     3
     4import yaml
     5
     6from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
     7from kat.harness import EDGE_STACK, Query
     8from tests.integration.manifests import namespace_manifest
     9
    10
    11class Empty(AmbassadorTest):
    12    single_namespace = True
    13    namespace = "empty-namespace"
    14    extra_ports = [8877]
    15
    16    def init(self):
    17        if EDGE_STACK:
    18            self.xfail = "XFailing for now"
    19        # Specify a non-default readiness port for test coverage purposes.
    20        # All other tests will use the default 8006 port.
    21        self.manifest_envs += """
    22    - name: AMBASSADOR_READY_PORT
    23      value: "8500"
    24"""
    25
    26    @classmethod
    27    def variants(cls) -> Generator[Node, None, None]:
    28        yield cls()
    29
    30    def manifests(self) -> str:
    31        return namespace_manifest("empty-namespace") + super().manifests()
    32
    33    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    34        yield from ()
    35
    36    def queries(self):
    37        yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"), phase=2)
    38        yield Query(self.url("_internal/v0/ping", scheme="http", port=8877), expected=403)
    39        yield Query(self.url("ambassador/v0/check_ready", scheme="http", port=8877))
    40
    41    def check(self):
    42        # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
    43        errors = self.results[0].json or []
    44
    45        # We shouldn't have any missing-CRD-types errors any more.
    46        for source, error in errors:
    47            if ("could not find" in error) and ("CRD definitions" in error):
    48                assert False, f"Missing CRDs: {error}"
    49
    50            if "Ingress resources" in error:
    51                assert False, f"Ingress resource error: {error}"
    52
    53        # The default errors assume that we have missing CRDs, and that's not correct any more,
    54        # so don't try to use assert_default_errors here.
    55
    56
    57class AmbassadorIDTest(AmbassadorTest):
    58
    59    target: ServiceType
    60
    61    def init(self):
    62        self.target = HTTP()
    63
    64    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    65        yield self, """
    66---
    67apiVersion: getambassador.io/v3alpha1
    68kind:  Module
    69name:  ambassador
    70config:
    71  use_ambassador_namespace_for_service_resolution: true
    72"""
    73        for prefix, amb_id in (
    74            ("findme", "[{self.ambassador_id}]"),
    75            ("findme-array", "[{self.ambassador_id}, missme]"),
    76            ("findme-array2", "[missme, {self.ambassador_id}]"),
    77            ("missme", "[missme]"),
    78            ("missme-array", "[missme1, missme2]"),
    79        ):
    80            yield self.target, self.format(
    81                """
    82---
    83apiVersion: getambassador.io/v3alpha1
    84kind: Mapping
    85name:  {self.path.k8s}-{prefix}
    86hostname: "*"
    87prefix: /{prefix}/
    88service: {self.target.path.fqdn}
    89ambassador_id: {amb_id}
    90            """,
    91                prefix=self.format(prefix),
    92                amb_id=self.format(amb_id),
    93            )
    94
    95    def queries(self):
    96        yield Query(self.url("findme/"))
    97        yield Query(self.url("findme-array/"))
    98        yield Query(self.url("findme-array2/"))
    99        yield Query(self.url("missme/"), expected=404)
   100        yield Query(self.url("missme-array/"), expected=404)
   101
   102
   103class InvalidResources(AmbassadorTest):
   104    target: ServiceType
   105
   106    def init(self):
   107        self.target = HTTP()
   108        self.resource_names = []
   109
   110        self.models = [
   111            """
   112apiVersion: getambassador.io/v3alpha1
   113kind:  AuthService
   114metadata:
   115  name:  {self.path.k8s}-as-bad1-<<WHICH>>
   116spec:
   117  ambassador_id: ["{self.ambassador_id}"]
   118  service_bad: {self.target.path.fqdn}
   119""",
   120            """
   121apiVersion: getambassador.io/v3alpha1
   122kind: Mapping
   123metadata:
   124  name:  {self.path.k8s}-m-good-<<WHICH>>
   125spec:
   126  ambassador_id: ["{self.ambassador_id}"]
   127  hostname: "*"
   128  prefix: /good-<<WHICH>>/
   129  service: {self.target.path.fqdn}
   130""",
   131            """
   132apiVersion: getambassador.io/v3alpha1
   133kind: Mapping
   134metadata:
   135  name:  {self.path.k8s}-m-bad-<<WHICH>>
   136spec:
   137  ambassador_id: ["{self.ambassador_id}"]
   138  hostname: "*"
   139  prefix_bad: /bad-<<WHICH>>/
   140  service: {self.target.path.fqdn}
   141""",
   142            """
   143kind: Mapping
   144metadata:
   145  name:  {self.path.k8s}-m-bad-no-apiversion-<<WHICH>>
   146spec:
   147  ambassador_id: ["{self.ambassador_id}"]
   148  hostname: "*"
   149  prefix_bad: /bad-<<WHICH>>/
   150  service: {self.target.path.fqdn}
   151""",
   152            """
   153apiVersion: getambassador.io/v3alpha1
   154kind:  Module
   155metadata:
   156  name:  {self.path.k8s}-md-bad-<<WHICH>>
   157spec:
   158  ambassador_id: ["{self.ambassador_id}"]
   159  config_bad: []
   160""",
   161            """
   162apiVersion: getambassador.io/v3alpha1
   163kind:  RateLimitService
   164metadata:
   165  name:  {self.path.k8s}-r-bad-<<WHICH>>
   166spec:
   167  ambassador_id: ["{self.ambassador_id}"]
   168  service_bad: {self.target.path.fqdn}
   169""",
   170            """
   171apiVersion: getambassador.io/v3alpha1
   172kind: TCPMapping
   173metadata:
   174  name:  {self.path.k8s}-tm-bad1-<<WHICH>>
   175spec:
   176  ambassador_id: ["{self.ambassador_id}"]
   177  service: {self.target.path.fqdn}
   178  port_bad: 8888
   179""",
   180            """
   181apiVersion: getambassador.io/v3alpha1
   182kind: TCPMapping
   183metadata:
   184  name:  {self.path.k8s}-tm-bad2-<<WHICH>>
   185spec:
   186  ambassador_id: ["{self.ambassador_id}"]
   187  service_bad: {self.target.path.fqdn}
   188  port: 8888
   189""",
   190            """
   191apiVersion: getambassador.io/v3alpha1
   192kind:  TracingService
   193metadata:
   194  name:  {self.path.k8s}-ts-bad1-<<WHICH>>
   195spec:
   196  ambassador_id: ["{self.ambassador_id}"]
   197  driver_bad: zipkin
   198  service: {self.target.path.fqdn}
   199""",
   200            """
   201apiVersion: getambassador.io/v3alpha1
   202kind:  TracingService
   203metadata:
   204  name:  {self.path.k8s}-ts-bad2-<<WHICH>>
   205spec:
   206  ambassador_id: ["{self.ambassador_id}"]
   207  driver: zipkin
   208  service_bad: {self.target.path.fqdn}
   209""",
   210        ]
   211
   212    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   213        counter = 0
   214
   215        for m_yaml in self.models:
   216            counter += 1
   217            m = yaml.safe_load(self.format(m_yaml.replace("<<WHICH>>", "annotation")))
   218
   219            for k in m["metadata"].keys():
   220                m[k] = m["metadata"][k]
   221            del m["metadata"]
   222
   223            for k in m["spec"].keys():
   224                if k == "ambassador_id":
   225                    continue
   226
   227                m[k] = m["spec"][k]
   228            del m["spec"]
   229
   230            if "good" not in m["name"]:
   231                # These all show up as "invalidresources.default.N" because they're
   232                # annotations.
   233                self.resource_names.append(f"invalidresources.default.{counter}")
   234
   235            yield self, yaml.dump(m)
   236
   237    def manifests(self):
   238        manifests = []
   239
   240        for m in self.models:
   241            m_yaml = self.format(m.replace("<<WHICH>>", "crd"))
   242            m_obj = yaml.safe_load(m_yaml)
   243            if "apiVersion" not in m_obj:
   244                continue
   245
   246            manifests.append("---")
   247            manifests.append(m_yaml)
   248
   249            if "good" not in m_obj["metadata"]["name"]:
   250                self.resource_names.append(m_obj["metadata"]["name"] + ".default.1")
   251
   252        return super().manifests() + "\n".join(manifests)
   253
   254    def queries(self):
   255        yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"))
   256
   257        yield Query(self.url("good-annotation/"), expected=200)
   258        yield Query(self.url("bad-annotation/"), expected=404)
   259        yield Query(self.url("good-crd/"), expected=200)
   260        yield Query(self.url("bad-crd/"), expected=404)
   261
   262    def check(self):
   263        # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
   264        errors = self.results[0].json or []
   265
   266        assert errors, "Invalid resources must generate errors, but we didn't get any"
   267
   268        error_dict = {}
   269
   270        for resource, error in errors:
   271            error_dict[resource] = error
   272
   273        for name in self.resource_names:
   274            assert name in error_dict, f"no error found for {name}"
   275
   276            error = error_dict[name]
   277
   278            # Check that the error is one of the errors that we expect.
   279            expected_errors = [
   280                re.compile(r"^.* in body is required$"),
   281                re.compile(r"^apiVersion None/ unsupported$"),
   282                re.compile(r'^spec\.config in body must be of type object: "null"$'),
   283            ]
   284            assert any(
   285                pat.match(error) for pat in expected_errors
   286            ), f"error for {name} should match one of the expected errors: {repr(error)}"
   287
   288
   289class ServerNameTest(AmbassadorTest):
   290
   291    target: ServiceType
   292
   293    def init(self):
   294        self.target = HTTP()
   295
   296    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   297        yield self, self.format(
   298            """
   299---
   300apiVersion: getambassador.io/v3alpha1
   301kind:  Module
   302name:  ambassador
   303config:
   304  server_name: "test-server"
   305---
   306apiVersion: getambassador.io/v3alpha1
   307kind: Mapping
   308name:  {self.path.k8s}/server-name
   309hostname: "*"
   310prefix: /server-name
   311service: {self.target.path.fqdn}
   312"""
   313        )
   314
   315    def queries(self):
   316        yield Query(self.url("server-name/"), expected=301)
   317
   318    def check(self):
   319        assert self.results[0].headers["Server"] == ["test-server"]
   320
   321
   322class SafeRegexMapping(AmbassadorTest):
   323
   324    target: ServiceType
   325
   326    def init(self):
   327        self.target = HTTP()
   328
   329    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   330        yield self, self.format(
   331            """
   332---
   333apiVersion: getambassador.io/v3alpha1
   334kind: Mapping
   335name:  {self.name}
   336prefix: /{self.name}/
   337prefix_regex: true
   338host: "[a-zA-Z].*"
   339host_regex: true
   340regex_headers:
   341  X-Foo: "^[a-z].*"
   342service: http://{self.target.path.fqdn}
   343"""
   344        )
   345
   346    def queries(self):
   347        yield Query(self.url(self.name + "/"), headers={"X-Foo": "hello"})
   348        yield Query(self.url(f"need-normalization/../{self.name}/"), headers={"X-Foo": "hello"})
   349        yield Query(self.url(self.name + "/"), expected=404)
   350        yield Query(self.url(f"need-normalization/../{self.name}/"), expected=404)
   351
   352    def check(self):
   353        for r in self.results:
   354            if r.backend:
   355                assert r.backend.name == self.target.path.k8s, (
   356                    r.backend.name,
   357                    self.target.path.k8s,
   358                )
   359                assert r.backend.request
   360                assert r.backend.request.headers["x-envoy-original-path"][0] == f"/{self.name}/"

View as plain text