...

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

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

     1from typing import ClassVar, Generator, Tuple, Union
     2
     3from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
     4from kat.harness import Query
     5from tests.selfsigned import TLSCerts
     6
     7SECRETS = (
     8    """
     9---
    10apiVersion: v1
    11metadata:
    12  name: {self.path.k8s}-client-cert-secret
    13data:
    14  tls.crt: """
    15    + TLSCerts["master.datawire.io"].k8s_crt
    16    + """
    17kind: Secret
    18type: Opaque
    19"""
    20)
    21
    22
    23class ConsulPod(ServiceType):
    24    skip_variant: ClassVar[bool] = True
    25    service_account_name: str
    26    datacenter_json: str
    27
    28    def __init__(self, service_account_name: str, datacenter_json: str, *args, **kwargs) -> None:
    29        self.service_account_name = service_account_name
    30        self.datacenter_json = datacenter_json
    31        kwargs[
    32            "service_manifests"
    33        ] = """
    34---
    35apiVersion: v1
    36kind: Service
    37metadata:
    38  name: {self.path.k8s}
    39spec:
    40  type: ClusterIP
    41  ports:
    42  - name: consul
    43    protocol: TCP
    44    port: 8500
    45    targetPort: 8500
    46  selector:
    47    backend: {self.path.k8s}
    48---
    49apiVersion: v1
    50kind: Pod
    51metadata:
    52  name: {self.path.k8s}
    53  annotations:
    54    sidecar.istio.io/inject: "false"
    55  labels:
    56    backend: {self.path.k8s}
    57spec:
    58  serviceAccountName: {self.service_account_name}
    59  containers:
    60  - name: consul
    61    image: consul:1.4.3
    62    env:
    63    - name: CONSUL_LOCAL_CONFIG
    64      value: "{self.datacenter_json}"
    65  restartPolicy: Always
    66"""
    67        super().__init__(*args, **kwargs)
    68
    69    def requirements(self):
    70        yield ("url", Query("http://%s:8500/ui/" % self.path.fqdn))
    71
    72
    73class ConsulTest(AmbassadorTest):
    74    k8s_target: ServiceType
    75    k8s_ns_target: ServiceType
    76    consul_target: ServiceType
    77
    78    def init(self):
    79        self.k8s_target = HTTP(name="k8s")
    80        self.k8s_ns_target = HTTP(name="k8s-ns", namespace="consul-test-namespace")
    81
    82        # This is the datacenter we'll use.
    83        self.datacenter = "dc12"
    84
    85        # We use Consul's local-config environment variable to set the datacenter name
    86        # on the actual Consul pod. That means that we need to supply the datacenter
    87        # name in JSON format.
    88        #
    89        # In a perfect world this would just be
    90        #
    91        # self.datacenter_dict = { "datacenter": self.datacenter }
    92        #
    93        # but the world is not perfect, so we have to supply it as JSON with LOTS of
    94        # escaping, since this gets passed through self.format (hence two layers of
    95        # doubled braces) and JSON decoding (hence backslash-escaped double quotes,
    96        # and of course the backslashes themselves have to be escaped...)
    97        self.consul_target = ConsulPod(
    98            service_account_name="consultest",  # `=self.name.k8s`, but self.name isn't set yet
    99            datacenter_json=f'{{{{\\"datacenter\\":\\"{self.datacenter}\\"}}}}',
   100        )
   101
   102    def manifests(self) -> str:
   103        # Unlike usual, we have stuff both before and after super().manifests():
   104        # we want the namespace early, but we want the superclass before our other
   105        # manifests, because of some magic with ServiceAccounts?
   106        return (
   107            self.format(
   108                """
   109---
   110apiVersion: v1
   111kind: Namespace
   112metadata:
   113  name: consul-test-namespace
   114"""
   115            )
   116            + super().manifests()
   117            + self.format(
   118                """
   119---
   120apiVersion: getambassador.io/v3alpha1
   121kind: ConsulResolver
   122metadata:
   123  name: {self.path.k8s}-resolver
   124spec:
   125  ambassador_id: [consultest]
   126  address: {self.consul_target.path.k8s}:$CONSUL_WATCHER_PORT
   127  datacenter: {self.datacenter}
   128---
   129apiVersion: getambassador.io/v3alpha1
   130kind: Mapping
   131metadata:
   132  name:  {self.path.k8s}-consul-ns-mapping
   133  namespace: consul-test-namespace
   134spec:
   135  ambassador_id: [consultest]
   136  hostname: "*"
   137  prefix: /{self.path.k8s}_consul_ns/
   138  service: {self.path.k8s}-consul-ns-service
   139  resolver: {self.path.k8s}-resolver
   140  load_balancer:
   141    policy: round_robin
   142"""
   143                + SECRETS
   144            )
   145        )
   146
   147    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   148        yield self.k8s_target, self.format(
   149            """
   150---
   151apiVersion: getambassador.io/v3alpha1
   152kind: Mapping
   153name:  {self.path.k8s}_k8s_mapping
   154hostname: "*"
   155prefix: /{self.path.k8s}_k8s/
   156service: {self.k8s_target.path.k8s}
   157---
   158apiVersion: getambassador.io/v3alpha1
   159kind: Mapping
   160name:  {self.path.k8s}_consul_mapping
   161hostname: "*"
   162prefix: /{self.path.k8s}_consul/
   163service: {self.path.k8s}-consul-service
   164# tls: {self.path.k8s}-client-context # this doesn't seem to work... ambassador complains with "no private key in secret ..."
   165resolver: {self.path.k8s}-resolver
   166load_balancer:
   167  policy: round_robin
   168---
   169apiVersion: getambassador.io/v3alpha1
   170kind: Mapping
   171name:  {self.path.k8s}_consul_node_mapping
   172hostname: "*"
   173prefix: /{self.path.k8s}_consul_node/ # this is testing that Ambassador correctly falls back to the `Address` if `Service.Address` does not exist
   174service: {self.path.k8s}-consul-node
   175# tls: {self.path.k8s}-client-context # this doesn't seem to work... ambassador complains with "no private key in secret ..."
   176resolver: {self.path.k8s}-resolver
   177load_balancer:
   178  policy: round_robin
   179---
   180kind:  TLSContext
   181name:  {self.path.k8s}-client-context
   182secret: {self.path.k8s}-client-cert-secret
   183---
   184apiVersion: getambassador.io/v3alpha1
   185kind: Host
   186name:  {self.path.k8s}-client-host
   187requestPolicy:
   188  insecure:
   189    action: Route
   190"""
   191        )
   192
   193    def queries(self):
   194        # Deregister the Consul services in phase 0.
   195        yield Query(
   196            self.format("http://{self.consul_target.path.k8s}:8500/v1/catalog/deregister"),
   197            method="PUT",
   198            body={
   199                "Datacenter": self.datacenter,
   200                "Node": self.format("{self.path.k8s}-consul-service"),
   201            },
   202            phase=0,
   203        )
   204        yield Query(
   205            self.format("http://{self.consul_target.path.k8s}:8500/v1/catalog/deregister"),
   206            method="PUT",
   207            body={
   208                "Datacenter": self.datacenter,
   209                "Node": self.format("{self.path.k8s}-consul-ns-service"),
   210            },
   211            phase=0,
   212        )
   213        yield Query(
   214            self.format("http://{self.consul_target.path.k8s}:8500/v1/catalog/deregister"),
   215            method="PUT",
   216            body={
   217                "Datacenter": self.datacenter,
   218                "Node": self.format("{self.path.k8s}-consul-node"),
   219            },
   220            phase=0,
   221        )
   222
   223        # The K8s service should be OK. The Consul services should 503 since they have no upstreams
   224        # in phase 1.
   225        yield Query(self.url(self.format("{self.path.k8s}_k8s/")), expected=200, phase=1)
   226        yield Query(self.url(self.format("{self.path.k8s}_consul/")), expected=503, phase=1)
   227        yield Query(self.url(self.format("{self.path.k8s}_consul_ns/")), expected=503, phase=1)
   228        yield Query(self.url(self.format("{self.path.k8s}_consul_node/")), expected=503, phase=1)
   229
   230        # Register the Consul services in phase 2.
   231        yield Query(
   232            self.format("http://{self.consul_target.path.k8s}:8500/v1/catalog/register"),
   233            method="PUT",
   234            body={
   235                "Datacenter": self.datacenter,
   236                "Node": self.format("{self.path.k8s}-consul-service"),
   237                "Address": self.k8s_target.path.k8s,
   238                "Service": {
   239                    "Service": self.format("{self.path.k8s}-consul-service"),
   240                    "Address": self.k8s_target.path.k8s,
   241                    "Port": 80,
   242                },
   243            },
   244            phase=2,
   245        )
   246        yield Query(
   247            self.format("http://{self.consul_target.path.k8s}:8500/v1/catalog/register"),
   248            method="PUT",
   249            body={
   250                "Datacenter": self.datacenter,
   251                "Node": self.format("{self.path.k8s}-consul-ns-service"),
   252                "Address": self.format("{self.k8s_ns_target.path.k8s}.consul-test-namespace"),
   253                "Service": {
   254                    "Service": self.format("{self.path.k8s}-consul-ns-service"),
   255                    "Address": self.format("{self.k8s_ns_target.path.k8s}.consul-test-namespace"),
   256                    "Port": 80,
   257                },
   258            },
   259            phase=2,
   260        )
   261        yield Query(
   262            self.format("http://{self.consul_target.path.k8s}:8500/v1/catalog/register"),
   263            method="PUT",
   264            body={
   265                "Datacenter": self.datacenter,
   266                "Node": self.format("{self.path.k8s}-consul-node"),
   267                "Address": self.k8s_target.path.k8s,
   268                "Service": {"Service": self.format("{self.path.k8s}-consul-node"), "Port": 80},
   269            },
   270            phase=2,
   271        )
   272
   273        # All services should work in phase 3.
   274        yield Query(self.url(self.format("{self.path.k8s}_k8s/")), expected=200, phase=3)
   275        yield Query(self.url(self.format("{self.path.k8s}_consul/")), expected=200, phase=3)
   276        yield Query(self.url(self.format("{self.path.k8s}_consul_ns/")), expected=200, phase=3)
   277        yield Query(self.url(self.format("{self.path.k8s}_consul_node/")), expected=200, phase=3)
   278
   279    def check(self):
   280        pass

View as plain text