...

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

Documentation: github.com/emissary-ingress/emissary/v3/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 self.results[0].backend
   286        assert self.results[0].backend.request
   287        assert len(self.results[0].backend.request.headers["l5d-dst-override"]) > 0
   288        assert self.results[0].backend.request.headers["l5d-dst-override"] == [
   289            "{}:80".format(self.target.path.fqdn)
   290        ]
   291        assert len(self.results[0].backend.request.headers["fruit"]) > 0
   292        assert self.results[0].backend.request.headers["fruit"] == ["banana"]
   293        assert len(self.results[0].backend.request.headers["x-evil-header"]) > 0
   294        assert self.results[0].backend.request.headers["x-evil-header"] == ["evilness"]
   295        assert "x-evilness" not in self.results[0].backend.request.headers
   296
   297        # [1]
   298        assert self.results[1].backend
   299        assert self.results[1].backend.request
   300        assert "l5d-dst-override" not in self.results[1].backend.request.headers
   301        assert len(self.results[1].backend.request.headers["fruit"]) > 0
   302        assert self.results[1].backend.request.headers["fruit"] == ["orange"]
   303        assert "x-evil-header" not in self.results[1].backend.request.headers
   304        assert len(self.results[1].backend.request.headers["x-evilness"]) > 0
   305        assert self.results[1].backend.request.headers["x-evilness"] == ["more evilness"]
   306
   307        # [2]
   308        assert self.results[2].backend
   309        assert self.results[2].backend.request
   310        assert len(self.results[2].backend.request.headers["l5d-dst-override"]) > 0
   311        assert self.results[2].backend.request.headers["l5d-dst-override"] == [
   312            "{}:80".format(self.target_add_linkerd_header_only.path.fqdn)
   313        ]
   314        assert len(self.results[2].backend.request.headers["x-evil-header"]) > 0
   315        assert self.results[2].backend.request.headers["x-evil-header"] == ["evilness"]
   316        assert len(self.results[2].backend.request.headers["x-evilness"]) > 0
   317        assert self.results[2].backend.request.headers["x-evilness"] == ["more evilness"]
   318
   319
   320class SameMappingDifferentNamespaces(AmbassadorTest):
   321    target: ServiceType
   322
   323    def init(self):
   324        self.target = HTTP()
   325
   326    def manifests(self) -> str:
   327        return (
   328            namespace_manifest("same-mapping-1")
   329            + namespace_manifest("same-mapping-2")
   330            + self.format(
   331                """
   332---
   333apiVersion: getambassador.io/v3alpha1
   334kind: Mapping
   335metadata:
   336  name: {self.target.path.k8s}
   337  namespace: same-mapping-1
   338spec:
   339  ambassador_id: [{self.ambassador_id}]
   340  hostname: "*"
   341  prefix: /{self.name}-1/
   342  service: {self.target.path.fqdn}
   343---
   344apiVersion: getambassador.io/v3alpha1
   345kind: Mapping
   346metadata:
   347  name: {self.target.path.k8s}
   348  namespace: same-mapping-2
   349spec:
   350  ambassador_id: [{self.ambassador_id}]
   351  hostname: "*"
   352  prefix: /{self.name}-2/
   353  service: {self.target.path.fqdn}
   354"""
   355            )
   356            + super().manifests()
   357        )
   358
   359    def queries(self):
   360        yield Query(self.url(self.name + "-1/"))
   361        yield Query(self.url(self.name + "-2/"))
   362
   363
   364class LongClusterNameMapping(AmbassadorTest):
   365    target: ServiceType
   366
   367    def init(self):
   368        self.target = HTTPBin()
   369
   370    def manifests(self) -> str:
   371        return (
   372            self.format(
   373                """
   374---
   375apiVersion: v1
   376kind: Service
   377metadata:
   378  name: thisisaverylongservicenameoverwithsixythreecharacters123456789
   379spec:
   380  type: ExternalName
   381  externalName: {self.target.path.fqdn}
   382---
   383apiVersion: getambassador.io/v3alpha1
   384kind: Mapping
   385metadata:
   386  name: {self.target.path.k8s}
   387spec:
   388  ambassador_id: [{self.ambassador_id}]
   389  hostname: "*"
   390  prefix: /{self.name}-1/
   391  service: thisisaverylongservicenameoverwithsixythreecharacters123456789
   392"""
   393            )
   394            + super().manifests()
   395        )
   396
   397    def queries(self):
   398        yield Query(self.url(self.name + "-1/"))

View as plain text