...

Text file src/github.com/datawire/ambassador/v2/python/tests/kat/t_basics.py

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

View as plain text