...

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

Documentation: github.com/datawire/ambassador/v2/python/tests/kat

     1from typing import Generator, Tuple, Union
     2
     3from abstract_tests import HTTP, AmbassadorTest, HTTPBin, Node, ServiceType
     4from ambassador.constants import Constants
     5from kat.harness import Query
     6from tests.integration.manifests import namespace_manifest
     7
     8# This is the place to add new MappingTests that run in the default namespace
     9
    10
    11# This has to be an `AmbassadorTest` because we're going to set up a Module that
    12# needs to apply to just this test. If this were a MappingTest, then the Module
    13# would apply to all other MappingTest's and we don't want that.
    14class HostHeaderMappingStripMatchingHostPort(AmbassadorTest):
    15    target: ServiceType
    16
    17    def init(self):
    18        self.target = HTTP()
    19
    20    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    21        yield self, self.format(
    22            """
    23---
    24apiVersion: getambassador.io/v3alpha1
    25kind:  Module
    26name:  ambassador
    27config:
    28  strip_matching_host_port: true
    29---
    30apiVersion: getambassador.io/v3alpha1
    31kind: Mapping
    32name:  {self.name}
    33prefix: /{self.name}/
    34service: http://{self.target.path.fqdn}
    35host: myhostname.com
    36"""
    37        )
    38
    39    def queries(self):
    40        # Sanity test that a missing or incorrect hostname does not route, and it does route with a correct hostname.
    41        yield Query(self.url(self.name + "/"), expected=404)
    42        yield Query(self.url(self.name + "/"), headers={"Host": "yourhostname.com"}, expected=404)
    43        yield Query(self.url(self.name + "/"), headers={"Host": "myhostname.com"})
    44        # Test that a host header with a port value that does match the listener's configured port is correctly
    45        # stripped for the purpose of routing, and matches the mapping.
    46        yield Query(
    47            self.url(self.name + "/"),
    48            headers={"Host": "myhostname.com:" + str(Constants.SERVICE_PORT_HTTP)},
    49        )
    50        # Test that a host header with a port value that does _not_ match the listener's configured does not have its
    51        # port value stripped for the purpose of routing, so it does not match the mapping.
    52        yield Query(
    53            self.url(self.name + "/"), headers={"Host": "myhostname.com:11875"}, expected=404
    54        )
    55
    56
    57# This has to be an `AmbassadorTest` because we're going to set up a Module that
    58# needs to apply to just this test. If this were a MappingTest, then the Module
    59# would apply to all other MappingTest's and we don't want that.
    60class MergeSlashesDisabled(AmbassadorTest):
    61    target: ServiceType
    62
    63    def init(self):
    64        self.target = HTTPBin()
    65
    66    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    67        yield self, self.format(
    68            """
    69---
    70apiVersion: getambassador.io/v3alpha1
    71kind: Mapping
    72name:  {self.name}
    73hostname: "*"
    74prefix: /{self.name}/status/
    75rewrite: /status/
    76service: {self.target.path.fqdn}
    77"""
    78        )
    79
    80    def queries(self):
    81        yield Query(self.url(self.name + "/status/200"))
    82        # Sanity test that an extra slash in the front of the request URL does not match the mapping,
    83        # since we did not set merge_slashes on the Ambassador module.
    84        yield Query(self.url("/" + self.name + "/status/200"), expected=404)
    85        yield Query(self.url("/" + self.name + "//status/200"), expected=404)
    86        yield Query(self.url(self.name + "//status/200"), expected=404)
    87
    88
    89# This has to be an `AmbassadorTest` because we're going to set up a Module that
    90# needs to apply to just this test. If this were a MappingTest, then the Module
    91# would apply to all other MappingTest's and we don't want that.
    92class MergeSlashesEnabled(AmbassadorTest):
    93    target: ServiceType
    94
    95    def init(self):
    96        self.target = HTTPBin()
    97
    98    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    99        yield self, self.format(
   100            """
   101---
   102apiVersion: getambassador.io/v3alpha1
   103kind:  Module
   104name:  ambassador
   105config:
   106  merge_slashes: true
   107---
   108apiVersion: getambassador.io/v3alpha1
   109kind: Mapping
   110name:  {self.name}
   111hostname: "*"
   112prefix: /{self.name}/status/
   113rewrite: /status/
   114service: {self.target.path.fqdn}
   115"""
   116        )
   117
   118    def queries(self):
   119        yield Query(self.url(self.name + "/status/200"))
   120        # Since merge_slashes is on the Ambassador module, extra slashes in the URL should not prevent the request
   121        # from matching.
   122        yield Query(self.url("/" + self.name + "/status/200"))
   123        yield Query(self.url("/" + self.name + "//status/200"))
   124        yield Query(self.url(self.name + "//status/200"))
   125
   126
   127# This has to be an `AmbassadorTest` because we're going to set up a Module that
   128# needs to apply to just this test. If this were a MappingTest, then the Module
   129# would apply to all other MappingTest's and we don't want that.
   130class RejectRequestsWithEscapedSlashesDisabled(AmbassadorTest):
   131    target: ServiceType
   132
   133    def init(self):
   134        self.target = HTTPBin()
   135
   136    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   137        yield self, self.format(
   138            """
   139---
   140apiVersion: getambassador.io/v3alpha1
   141kind: Mapping
   142name:  {self.name}
   143hostname: "*"
   144prefix: /{self.name}/status/
   145rewrite: /status/
   146service: {self.target.path.fqdn}
   147"""
   148        )
   149
   150    def queries(self):
   151        # Sanity test that escaped slashes are not rejected by default. The upstream
   152        # httpbin server doesn't know what to do with this request, though, so expect
   153        # a 404. In another test, we'll expect HTTP 400 with reject_requests_with_escaped_slashes
   154        yield Query(self.url(self.name + "/status/%2F200"), expected=404)
   155
   156    def check(self):
   157        # We should have observed this 404 upstream from httpbin. The presence of this header verifies that.
   158        print("headers=%s", repr(self.results[0].headers))
   159        assert "X-Envoy-Upstream-Service-Time" in self.results[0].headers
   160
   161
   162# This has to be an `AmbassadorTest` because we're going to set up a Module that
   163# needs to apply to just this test. If this were a MappingTest, then the Module
   164# would apply to all other MappingTest's and we don't want that.
   165class RejectRequestsWithEscapedSlashesEnabled(AmbassadorTest):
   166    target: ServiceType
   167
   168    def init(self):
   169        self.target = HTTPBin()
   170
   171    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   172        yield self, self.format(
   173            """
   174---
   175apiVersion: getambassador.io/v3alpha1
   176kind:  Module
   177name:  ambassador
   178config:
   179  reject_requests_with_escaped_slashes: true
   180---
   181apiVersion: getambassador.io/v3alpha1
   182kind: Mapping
   183name:  {self.name}
   184hostname: "*"
   185prefix: /{self.name}/status/
   186rewrite: /status/
   187service: {self.target.path.fqdn}
   188"""
   189        )
   190
   191    def queries(self):
   192        # Expect that requests with escaped slashes are rejected by Envoy. We know this is rejected
   193        # by envoy because in a previous test, without the reject_requests_with_escaped_slashes,
   194        # this same request got status 404.
   195        yield Query(self.url(self.name + "/status/%2F200"), expected=400)
   196
   197    def check(self):
   198        # We should have not have observed this 400 upstream from httpbin. The absence of this header
   199        # suggests that (though does not prove, in theory).
   200        assert "X-Envoy-Upstream-Service-Time" not in self.results[0].headers
   201
   202
   203class LinkerdHeaderMapping(AmbassadorTest):
   204    target: ServiceType
   205
   206    def init(self):
   207        self.target = HTTP()
   208        self.target_no_header = HTTP(name="noheader")
   209        self.target_add_linkerd_header_only = HTTP(name="addlinkerdonly")
   210
   211    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   212        yield self, self.format(
   213            """
   214---
   215apiVersion: getambassador.io/v3alpha1
   216kind:  Module
   217name:  ambassador
   218config:
   219  add_linkerd_headers: true
   220  defaults:
   221    httpmapping:
   222        add_request_headers:
   223            fruit:
   224                append: False
   225                value: orange
   226        remove_request_headers:
   227        - x-evil-header
   228---
   229apiVersion: getambassador.io/v3alpha1
   230kind: Mapping
   231name: {self.target_add_linkerd_header_only.path.k8s}
   232hostname: "*"
   233prefix: /target_add_linkerd_header_only/
   234service: {self.target_add_linkerd_header_only.path.fqdn}
   235add_request_headers: {{}}
   236remove_request_headers: []
   237---
   238apiVersion: getambassador.io/v3alpha1
   239kind: Mapping
   240name: {self.target_no_header.path.k8s}
   241hostname: "*"
   242prefix: /target_no_header/
   243service: {self.target_no_header.path.fqdn}
   244add_linkerd_headers: false
   245---
   246apiVersion: getambassador.io/v3alpha1
   247kind: Mapping
   248name: {self.target.path.k8s}
   249hostname: "*"
   250prefix: /target/
   251service: {self.target.path.fqdn}
   252add_request_headers:
   253    fruit:
   254        append: False
   255        value: banana
   256remove_request_headers:
   257- x-evilness
   258"""
   259        )
   260
   261    def queries(self):
   262        # [0] expect Linkerd headers set through mapping
   263        yield Query(
   264            self.url("target/"),
   265            headers={"x-evil-header": "evilness", "x-evilness": "more evilness"},
   266            expected=200,
   267        )
   268
   269        # [1] expect no Linkerd headers
   270        yield Query(
   271            self.url("target_no_header/"),
   272            headers={"x-evil-header": "evilness", "x-evilness": "more evilness"},
   273            expected=200,
   274        )
   275
   276        # [2] expect Linkerd headers only
   277        yield Query(
   278            self.url("target_add_linkerd_header_only/"),
   279            headers={"x-evil-header": "evilness", "x-evilness": "more evilness"},
   280            expected=200,
   281        )
   282
   283    def check(self):
   284        # [0]
   285        assert len(self.results[0].backend.request.headers["l5d-dst-override"]) > 0
   286        assert self.results[0].backend.request.headers["l5d-dst-override"] == [
   287            "{}:80".format(self.target.path.fqdn)
   288        ]
   289        assert len(self.results[0].backend.request.headers["fruit"]) > 0
   290        assert self.results[0].backend.request.headers["fruit"] == ["banana"]
   291        assert len(self.results[0].backend.request.headers["x-evil-header"]) > 0
   292        assert self.results[0].backend.request.headers["x-evil-header"] == ["evilness"]
   293        assert "x-evilness" not in self.results[0].backend.request.headers
   294
   295        # [1]
   296        assert "l5d-dst-override" not in self.results[1].backend.request.headers
   297        assert len(self.results[1].backend.request.headers["fruit"]) > 0
   298        assert self.results[1].backend.request.headers["fruit"] == ["orange"]
   299        assert "x-evil-header" not in self.results[1].backend.request.headers
   300        assert len(self.results[1].backend.request.headers["x-evilness"]) > 0
   301        assert self.results[1].backend.request.headers["x-evilness"] == ["more evilness"]
   302
   303        # [2]
   304        assert len(self.results[2].backend.request.headers["l5d-dst-override"]) > 0
   305        assert self.results[2].backend.request.headers["l5d-dst-override"] == [
   306            "{}:80".format(self.target_add_linkerd_header_only.path.fqdn)
   307        ]
   308        assert len(self.results[2].backend.request.headers["x-evil-header"]) > 0
   309        assert self.results[2].backend.request.headers["x-evil-header"] == ["evilness"]
   310        assert len(self.results[2].backend.request.headers["x-evilness"]) > 0
   311        assert self.results[2].backend.request.headers["x-evilness"] == ["more evilness"]
   312
   313
   314class SameMappingDifferentNamespaces(AmbassadorTest):
   315    target: ServiceType
   316
   317    def init(self):
   318        self.target = HTTP()
   319
   320    def manifests(self) -> str:
   321        return (
   322            namespace_manifest("same-mapping-1")
   323            + namespace_manifest("same-mapping-2")
   324            + self.format(
   325                """
   326---
   327apiVersion: getambassador.io/v3alpha1
   328kind: Mapping
   329metadata:
   330  name: {self.target.path.k8s}
   331  namespace: same-mapping-1
   332spec:
   333  ambassador_id: [{self.ambassador_id}]
   334  hostname: "*"
   335  prefix: /{self.name}-1/
   336  service: {self.target.path.fqdn}
   337---
   338apiVersion: getambassador.io/v3alpha1
   339kind: Mapping
   340metadata:
   341  name: {self.target.path.k8s}
   342  namespace: same-mapping-2
   343spec:
   344  ambassador_id: [{self.ambassador_id}]
   345  hostname: "*"
   346  prefix: /{self.name}-2/
   347  service: {self.target.path.fqdn}
   348"""
   349            )
   350            + super().manifests()
   351        )
   352
   353    def queries(self):
   354        yield Query(self.url(self.name + "-1/"))
   355        yield Query(self.url(self.name + "-2/"))
   356
   357
   358class LongClusterNameMapping(AmbassadorTest):
   359    target: ServiceType
   360
   361    def init(self):
   362        self.target = HTTPBin()
   363
   364    def manifests(self) -> str:
   365        return (
   366            self.format(
   367                """
   368---
   369apiVersion: v1
   370kind: Service
   371metadata:
   372  name: thisisaverylongservicenameoverwithsixythreecharacters123456789
   373spec:
   374  type: ExternalName
   375  externalName: {self.target.path.fqdn}
   376---
   377apiVersion: getambassador.io/v3alpha1
   378kind: Mapping
   379metadata:
   380  name: {self.target.path.k8s}
   381spec:
   382  ambassador_id: [{self.ambassador_id}]
   383  hostname: "*"
   384  prefix: /{self.name}-1/
   385  service: thisisaverylongservicenameoverwithsixythreecharacters123456789
   386"""
   387            )
   388            + super().manifests()
   389        )
   390
   391    def queries(self):
   392        yield Query(self.url(self.name + "-1/"))

View as plain text