From 9c1645af81fd01d3cd91cc367c97bb684258b4b9 Mon Sep 17 00:00:00 2001 From: Xunzhuo Date: Sat, 7 Jan 2023 03:05:52 +0800 Subject: [PATCH] feat: add support for TCP Route (#737) Signed-off-by: bitliu Fixes: https://github.com/envoyproxy/gateway/issues/643 --- docs/latest/user/tcp-routing.md | 283 +++++++++ docs/latest/user_docs.rst | 1 + examples/kubernetes/tcp-routing.yaml | 148 +++++ internal/gatewayapi/contexts.go | 92 ++- internal/gatewayapi/helpers.go | 24 +- internal/gatewayapi/listener.go | 8 +- internal/gatewayapi/resource.go | 1 + internal/gatewayapi/route.go | 128 +++- internal/gatewayapi/runner/runner.go | 4 + ...th-listener-with-tcp-with-hostname.in.yaml | 16 + ...h-listener-with-tcp-with-hostname.out.yaml | 40 ++ ...proute-with-mismatch-port-protocol.in.yaml | 29 + ...route-with-mismatch-port-protocol.out.yaml | 75 +++ ...th-tcproute-with-multiple-backends.in.yaml | 33 + ...h-tcproute-with-multiple-backends.out.yaml | 79 +++ ...-with-tcproute-with-multiple-rules.in.yaml | 34 ++ ...with-tcproute-with-multiple-rules.out.yaml | 80 +++ ...h-listener-with-unmatched-tcproute.in.yaml | 29 + ...-listener-with-unmatched-tcproute.out.yaml | 47 ++ ...listener-with-unsupported-protocol.in.yaml | 2 +- ...istener-with-unsupported-protocol.out.yaml | 4 +- ...e-listener-with-multiple-tcproutes.in.yaml | 42 ++ ...-listener-with-multiple-tcproutes.out.yaml | 103 ++++ ...ith-two-listeners-on-same-tcp-port.in.yaml | 35 ++ ...th-two-listeners-on-same-tcp-port.out.yaml | 98 +++ ...s-with-same-port-http-tcp-protocol.in.yaml | 53 ++ ...-with-same-port-http-tcp-protocol.out.yaml | 142 +++++ ...rs-with-tcproutes-with-sectionname.in.yaml | 50 ++ ...s-with-tcproutes-with-sectionname.out.yaml | 134 +++++ ...with-tcproutes-without-sectionname.in.yaml | 48 ++ ...ith-tcproutes-without-sectionname.out.yaml | 130 ++++ internal/gatewayapi/translator.go | 12 +- internal/gatewayapi/translator_test.go | 1 + internal/gatewayapi/validate.go | 2 +- internal/gatewayapi/zz_generated.deepcopy.go | 11 + internal/ir/infra.go | 3 + internal/ir/xds_test.go | 20 +- internal/message/types.go | 2 + .../provider/kubernetes/config/rbac/role.yaml | 2 + internal/provider/kubernetes/controller.go | 562 +++++++++++------- internal/provider/kubernetes/predicates.go | 9 + .../provider/kubernetes/predicates_test.go | 12 + internal/provider/kubernetes/rbac.go | 4 +- internal/provider/kubernetes/routes.go | 57 ++ internal/provider/kubernetes/test/utils.go | 28 + .../in/tcproute-experimental-crd.yaml | 518 ++++++++++++++++ internal/xds/translator/listener.go | 20 + .../xds-ir/http-route-weighted-backend.yaml | 21 + .../multiple-simple-tcp-route-same-port.yaml | 41 ++ .../testdata/in/xds-ir/tcp-route-complex.yaml | 14 + .../testdata/in/xds-ir/tcp-route-simple.yaml | 9 + .../in/xds-ir/tcp-route-weighted-backend.yaml | 22 + .../http-route-weighted-backend.clusters.yaml | 37 ++ ...http-route-weighted-backend.listeners.yaml | 40 ++ .../http-route-weighted-backend.routes.yaml | 10 + ...e-simple-tcp-route-same-port.clusters.yaml | 115 ++++ ...-simple-tcp-route-same-port.listeners.yaml | 70 +++ ...ple-simple-tcp-route-same-port.routes.yaml | 1 + .../xds-ir/tcp-route-complex.clusters.yaml | 23 + .../xds-ir/tcp-route-complex.listeners.yaml | 35 ++ .../out/xds-ir/tcp-route-complex.routes.yaml | 1 + .../out/xds-ir/tcp-route-simple.clusters.yaml | 23 + .../xds-ir/tcp-route-simple.listeners.yaml | 26 + .../out/xds-ir/tcp-route-simple.routes.yaml | 1 + .../tcp-route-weighted-backend.clusters.yaml | 37 ++ .../tcp-route-weighted-backend.listeners.yaml | 35 ++ .../tcp-route-weighted-backend.routes.yaml | 1 + internal/xds/translator/translator.go | 58 +- internal/xds/translator/translator_test.go | 15 + 69 files changed, 3609 insertions(+), 281 deletions(-) create mode 100644 docs/latest/user/tcp-routing.md create mode 100644 examples/kubernetes/tcp-routing.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml create mode 100644 internal/provider/kubernetes/testdata/in/tcproute-experimental-crd.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.routes.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.routes.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.routes.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.routes.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.routes.yaml diff --git a/docs/latest/user/tcp-routing.md b/docs/latest/user/tcp-routing.md new file mode 100644 index 00000000000..dbf8c145c19 --- /dev/null +++ b/docs/latest/user/tcp-routing.md @@ -0,0 +1,283 @@ +# TCP Routing + +[TCPRoute][] provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. To learn more about HTTP routing, refer to the [Gateway API documentation][]. + +Follow the steps to install Envoy Gateway and the example manifest. + +In this example, we have one Gateway resource and two TCPRoute resources that distribute the traffic with the following rules: + +All TCP streams on port `8088` of the Gateway are forwarded to port 3001 of `foo` Kubernetes Service. +All TCP streams on port `8089` of the Gateway are forwarded to port 3002 of `bar` Kubernetes Service. +In this example two TCP listeners will be applied to the Gateway in order to route them to two separate backend TCPRoutes, note that the protocol set for the listeners on the Gateway is TCP: + +Install the GatewayClass and a `tcp-gateway` Gateway first. + +```shell +cat < 0 { + continue + } + if !listener.IsReady() { + continue + } + accepted = true + irKey := irStringKey(listener.gateway) + containerPort := servicePortToContainerPort(int32(listener.Port)) + // Create the TCP Listener while parsing the TCPRoute since + // the listener directly links to a routeDestination. + irListener := &ir.TCPListener{ + Name: irTCPListenerName(listener, tcpRoute), + Address: "0.0.0.0", + Port: uint32(containerPort), + Destinations: routeDestinations, + } + gwXdsIR := xdsIR[irKey] + gwXdsIR.TCP = append(gwXdsIR.TCP, irListener) + + // Theoretically there should only be one parent ref per + // Route that attaches to a given Listener, so fine to just increment here, but we + // might want to check to ensure we're not double-counting. + if len(routeDestinations) > 0 { + listener.IncrementAttachedRoutes() + } + } + + // If no negative conditions have been set, the route is considered "Accepted=True". + if accepted && parentRef.tcpRoute != nil && + len(parentRef.tcpRoute.Status.Parents[parentRef.routeParentStatusIdx].Conditions) == 0 { + parentRef.SetCondition(tcpRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionTrue, + v1beta1.RouteReasonAccepted, + "Route is accepted", + ) + } + if !accepted { + parentRef.SetCondition(tcpRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + v1beta1.RouteReasonUnsupportedValue, + "Multiple routes on the same TCP listener", + ) + } + } +} + // processRuleRouteDestination takes a backendRef and translates it into a destination or sets error statuses and // returns the weight for the backend so that 500 error responses can be returned for invalid backends in // the same proportion as the backend would have otherwise received diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 71c0edd00a0..ebe994b0842 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -111,6 +111,10 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { key := utils.NamespacedName(tlsRoute) r.ProviderResources.TLSRouteStatuses.Store(key, tlsRoute) } + for _, tcpRoute := range result.TCPRoutes { + key := utils.NamespacedName(tcpRoute) + r.ProviderResources.TCPRouteStatuses.Store(key, tcpRoute) + } for _, udpRoute := range result.UDPRoutes { key := utils.NamespacedName(udpRoute) r.ProviderResources.UDPRouteStatuses.Store(key, udpRoute) diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.in.yaml new file mode 100644 index 00000000000..1be8e3b6948 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.in.yaml @@ -0,0 +1,16 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + hostname: foo.com + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.out.yaml new file mode 100644 index 00000000000..83469a9ffeb --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcp-with-hostname.out.yaml @@ -0,0 +1,40 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + hostname: foo.com + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + conditions: + - type: Programmed + status: "False" + reason: Invalid + message: Listener must not have hostname set when protocol is TCP. +xdsIR: + envoy-gateway-gateway-1: {} +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.in.yaml new file mode 100644 index 00000000000..049bb0ea059 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.in.yaml @@ -0,0 +1,29 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8081 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml new file mode 100644 index 00000000000..f9afdc09164 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-mismatch-port-protocol.out.yaml @@ -0,0 +1,75 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 0 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8081 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - type: ResolvedRefs + status: "False" + reason: PortNotFound + message: TCP Port 8081 not found on service default/service-1 +xdsIR: + envoy-gateway-gateway-1: {} + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp + protocol: "TCP" + servicePort: 162 + containerPort: 10162 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.in.yaml new file mode 100644 index 00000000000..3e5271710c3 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.in.yaml @@ -0,0 +1,33 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + weight: 50 + - name: service-2 + port: 8080 + weight: 50 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.out.yaml new file mode 100644 index 00000000000..3cb1726e263 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-backends.out.yaml @@ -0,0 +1,79 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 0 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + weight: 50 + - name: service-2 + port: 8080 + weight: 50 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - type: ResolvedRefs + status: "False" + reason: InvalidBackend + message: One and only one backend is supported +xdsIR: + envoy-gateway-gateway-1: {} + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp + protocol: "TCP" + servicePort: 80 + containerPort: 10080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.in.yaml new file mode 100644 index 00000000000..52cfed69655 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.in.yaml @@ -0,0 +1,34 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + weight: 50 + - backendRefs: + - name: service-2 + port: 8080 + weight: 50 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml new file mode 100644 index 00000000000..3f171787479 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tcproute-with-multiple-rules.out.yaml @@ -0,0 +1,80 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 0 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8080 + weight: 50 + - backendRefs: + - name: service-2 + port: 8080 + weight: 50 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - type: ResolvedRefs + status: "False" + reason: InvalidRule + message: One and only one rule is supported +xdsIR: + envoy-gateway-gateway-1: {} + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp + protocol: "TCP" + servicePort: 80 + containerPort: 10080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.in.yaml new file mode 100644 index 00000000000..dc4f8e80521 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.in.yaml @@ -0,0 +1,29 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-2 + rules: + - backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.out.yaml new file mode 100644 index 00000000000..8144ea49bac --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-unmatched-tcproute.out.yaml @@ -0,0 +1,47 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 0 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready + +xdsIR: + envoy-gateway-gateway-1: {} + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp + protocol: "TCP" + servicePort: 80 + containerPort: 10080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.in.yaml index cef4eb2b256..7fe1283b99c 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.in.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.in.yaml @@ -8,7 +8,7 @@ gateways: gatewayClassName: envoy-gateway-class listeners: - name: unsupported - protocol: TCP + protocol: GRPC port: 80 allowedRoutes: namespaces: diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.out.yaml index 2cbd7035f20..0a7e99ecf1b 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-unsupported-protocol.out.yaml @@ -8,7 +8,7 @@ gateways: gatewayClassName: envoy-gateway-class listeners: - name: unsupported - protocol: TCP + protocol: GRPC port: 80 allowedRoutes: namespaces: @@ -21,7 +21,7 @@ gateways: - type: Accepted status: "False" reason: UnsupportedProtocol - message: Protocol TCP is unsupported, must be HTTP, HTTPS, or UDP. + message: Protocol GRPC is unsupported, must be HTTP, HTTPS, TCP or UDP. - type: Programmed status: "False" reason: Invalid diff --git a/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.in.yaml b/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.in.yaml new file mode 100644 index 00000000000..13195729173 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.in.yaml @@ -0,0 +1,42 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8163 diff --git a/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml b/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml new file mode 100644 index 00000000000..8348d196a2d --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml @@ -0,0 +1,103 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "False" + reason: UnsupportedValue + message: Multiple routes on the same TCP listener +xdsIR: + envoy-gateway-gateway-1: + tcp: + - name: "envoy-gateway-gateway-1-tcp-tcproute-1" + address: "0.0.0.0" + port: 10162 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp + protocol: "TCP" + servicePort: 162 + containerPort: 10162 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.in.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.in.yaml new file mode 100644 index 00000000000..a53076da009 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.in.yaml @@ -0,0 +1,35 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml new file mode 100644 index 00000000000..676829906c5 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml @@ -0,0 +1,98 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready + - name: tcp2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 0 + conditions: + - type: Conflicted + status: "True" + reason: ProtocolConflict + message: Only one TCP listener is allowed in a given port + - type: Programmed + status: "False" + reason: Invalid + message: Listener is invalid, see other Conditions for details. +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + envoy-gateway-gateway-1: + tcp: + - name: "envoy-gateway-gateway-1-tcp1-tcproute-1" + address: "0.0.0.0" + port: 10162 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp1 + protocol: "TCP" + servicePort: 162 + containerPort: 10162 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.in.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.in.yaml new file mode 100644 index 00000000000..a7296205001 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.in.yaml @@ -0,0 +1,53 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*" + allowedRoutes: + namespaces: + from: All + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml new file mode 100644 index 00000000000..1e85bd35333 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml @@ -0,0 +1,142 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*" + allowedRoutes: + namespaces: + from: All + - name: tcp + protocol: TCP + port: 80 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready + - name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +tcpRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + envoy-gateway-gateway-1: + http: + - name: envoy-gateway-gateway-1-http + address: 0.0.0.0 + port: 10080 + hostnames: + - "*" + routes: + - name: default-httproute-1-rule-0-match-0-* + pathMatch: + prefix: "/" + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 + tcp: + - name: "envoy-gateway-gateway-1-tcp-tcproute-1" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: http + protocol: "HTTP" + servicePort: 80 + containerPort: 10080 + - name: tcp + protocol: "TCP" + servicePort: 80 + containerPort: 10080 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.in.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.in.yaml new file mode 100644 index 00000000000..dbdd26a3d06 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.in.yaml @@ -0,0 +1,50 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 163 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: tcp1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: tcp2 + rules: + - backendRefs: + - name: service-2 + port: 8163 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml new file mode 100644 index 00000000000..7974ccc6115 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml @@ -0,0 +1,134 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 163 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready + - name: tcp2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: tcp1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + sectionName: tcp1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: tcp2 + rules: + - backendRefs: + - name: service-2 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + sectionName: tcp2 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted +xdsIR: + envoy-gateway-gateway-1: + tcp: + - name: "envoy-gateway-gateway-1-tcp1-tcproute-1" + address: "0.0.0.0" + port: 10162 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 + - name: "envoy-gateway-gateway-1-tcp2-tcproute-2" + address: "0.0.0.0" + port: 10163 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp1 + protocol: "TCP" + servicePort: 162 + containerPort: 10162 + - name: tcp2 + protocol: "TCP" + servicePort: 163 + containerPort: 10163 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.in.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.in.yaml new file mode 100644 index 00000000000..773c926237d --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.in.yaml @@ -0,0 +1,48 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 161 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8163 diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml new file mode 100644 index 00000000000..9e933fadc9a --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml @@ -0,0 +1,130 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tcp1 + protocol: TCP + port: 161 + allowedRoutes: + namespaces: + from: All + - name: tcp2 + protocol: TCP + port: 162 + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tcp1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready + - name: tcp2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute + AttachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Listener is ready +tcpRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-1 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TCPRoute + metadata: + namespace: default + name: tcproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8163 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "False" + reason: UnsupportedValue + message: Multiple routes on the same TCP listener +xdsIR: + envoy-gateway-gateway-1: + tcp: + - name: "envoy-gateway-gateway-1-tcp1-tcproute-1" + address: "0.0.0.0" + port: 10161 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 + - name: "envoy-gateway-gateway-1-tcp2-tcproute-1" + address: "0.0.0.0" + port: 10162 + destinations: + - host: "7.7.7.7" + port: 8163 + weight: 0 + +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + name: envoy-gateway-gateway-1 + image: envoyproxy/envoy:translator-tests + listeners: + - address: "" + ports: + - name: tcp1 + protocol: "TCP" + servicePort: 161 + containerPort: 10161 + - name: tcp2 + protocol: "TCP" + servicePort: 162 + containerPort: 10162 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 2d70eeb37a6..71a9b3aa842 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -15,6 +15,7 @@ const ( KindHTTPRoute = "HTTPRoute" KindGRPCRoute = "GRPCRoute" KindTLSRoute = "TLSRoute" + KindTCPRoute = "TCPRoute" KindUDPRoute = "UDPRoute" KindService = "Service" KindSecret = "Secret" @@ -62,13 +63,14 @@ type TranslateResult struct { Gateways []*v1beta1.Gateway HTTPRoutes []*v1beta1.HTTPRoute TLSRoutes []*v1alpha2.TLSRoute + TCPRoutes []*v1alpha2.TCPRoute UDPRoutes []*v1alpha2.UDPRoute XdsIR XdsIRMap InfraIR InfraIRMap } func newTranslateResult(gateways []*GatewayContext, - httpRoutes []*HTTPRouteContext, tlsRoutes []*TLSRouteContext, udpRoutes []*UDPRouteContext, xdsIR XdsIRMap, + httpRoutes []*HTTPRouteContext, tlsRoutes []*TLSRouteContext, tcpRoutes []*TCPRouteContext, udpRoutes []*UDPRouteContext, xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { translateResult := &TranslateResult{ XdsIR: xdsIR, @@ -84,6 +86,9 @@ func newTranslateResult(gateways []*GatewayContext, for _, tlsRoute := range tlsRoutes { translateResult.TLSRoutes = append(translateResult.TLSRoutes, tlsRoute.TLSRoute) } + for _, tcpRoute := range tcpRoutes { + translateResult.TCPRoutes = append(translateResult.TCPRoutes, tcpRoute.TCPRoute) + } for _, udpRoute := range udpRoutes { translateResult.UDPRoutes = append(translateResult.UDPRoutes, udpRoute.UDPRoute) } @@ -107,13 +112,16 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { // Process all relevant TLSRoutes. tlsRoutes := t.ProcessTLSRoutes(resources.TLSRoutes, gateways, resources, xdsIR) + // Process all relevant TCPRoutes. + tcpRoutes := t.ProcessTCPRoutes(resources.TCPRoutes, gateways, resources, xdsIR) + // Process all relevant UDPRoutes. udpRoutes := t.ProcessUDPRoutes(resources.UDPRoutes, gateways, resources, xdsIR) // Sort xdsIR based on the Gateway API spec sortXdsIRMap(xdsIR) - return newTranslateResult(gateways, httpRoutes, tlsRoutes, udpRoutes, xdsIR, infraIR) + return newTranslateResult(gateways, httpRoutes, tlsRoutes, tcpRoutes, udpRoutes, xdsIR, infraIR) } func (t *Translator) GetRelevantGateways(gateways []*v1beta1.Gateway) []*GatewayContext { diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index 0b0ca2b1dda..7e9739ed804 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -64,6 +64,7 @@ func TestTranslate(t *testing.T) { Ports: []v1.ServicePort{ {Port: 8080, Protocol: v1.ProtocolTCP}, {Port: 8443, Protocol: v1.ProtocolTCP}, + {Port: 8163, Protocol: v1.ProtocolTCP}, {Port: 8162, Protocol: v1.ProtocolUDP}, }, }, diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go index 33da36607f3..0bfabb51fa8 100644 --- a/internal/gatewayapi/validate.go +++ b/internal/gatewayapi/validate.go @@ -419,7 +419,7 @@ func (t *Translator) validateConflictedLayer7Listeners(gateways []*GatewayContex for _, gateway := range gateways { portListenerInfo := map[v1beta1.PortNumber]*portListeners{} for _, listener := range gateway.listeners { - if listener.Protocol == v1beta1.UDPProtocolType { + if listener.Protocol == v1beta1.UDPProtocolType || listener.Protocol == v1beta1.TCPProtocolType { continue } if portListenerInfo[listener.Port] == nil { diff --git a/internal/gatewayapi/zz_generated.deepcopy.go b/internal/gatewayapi/zz_generated.deepcopy.go index 288eeb96e24..f6e3ace023c 100644 --- a/internal/gatewayapi/zz_generated.deepcopy.go +++ b/internal/gatewayapi/zz_generated.deepcopy.go @@ -64,6 +64,17 @@ func (in *Resources) DeepCopyInto(out *Resources) { } } } + if in.TCPRoutes != nil { + in, out := &in.TCPRoutes, &out.TCPRoutes + *out = make([]*v1alpha2.TCPRoute, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1alpha2.TCPRoute) + (*in).DeepCopyInto(*out) + } + } + } if in.UDPRoutes != nil { in, out := &in.UDPRoutes, &out.UDPRoutes *out = make([]*v1alpha2.UDPRoute, len(*in)) diff --git a/internal/ir/infra.go b/internal/ir/infra.go index 4209a2f4a0e..ef8d310d40a 100644 --- a/internal/ir/infra.go +++ b/internal/ir/infra.go @@ -88,6 +88,9 @@ const ( // TLSProtocolType accepts TLS sessions over TCP. TLSProtocolType ProtocolType = "TLS" + // TCPProtocolType accepts TCP connection. + TCPProtocolType ProtocolType = "TCP" + // UDPProtocolType accepts UDP connection. UDPProtocolType ProtocolType = "UDP" ) diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 7e10a7779f0..3e775622362 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -69,6 +69,12 @@ var ( TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, Destinations: []*RouteDestination{&happyRouteDestination}, } + emptySNITCPListenerTLSPassthrough = TCPListener{ + Name: "empty-sni", + Address: "0.0.0.0", + Port: 80, + Destinations: []*RouteDestination{&happyRouteDestination}, + } invalidNameTCPListenerTLSPassthrough = TCPListener{ Address: "0.0.0.0", Port: 80, @@ -525,6 +531,11 @@ func TestValidateTCPListener(t *testing.T) { input: happyTCPListenerTLSPassthrough, want: nil, }, + { + name: "tcp empty SNIs", + input: emptySNITCPListenerTLSPassthrough, + want: nil, + }, { name: "tls passthrough invalid name", input: invalidNameTCPListenerTLSPassthrough, @@ -794,7 +805,14 @@ func TestValidateRouteDestination(t *testing.T) { want: ErrRouteDestinationHostInvalid, }, { - name: "invalid port", + name: "missing ip", + input: RouteDestination{ + Port: 8080, + }, + want: ErrRouteDestinationHostInvalid, + }, + { + name: "missing port", input: RouteDestination{ Host: "10.11.12.13", }, diff --git a/internal/message/types.go b/internal/message/types.go index 7b8542cc8fa..4efef215ced 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -26,6 +26,7 @@ type ProviderResources struct { HTTPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1b1.HTTPRoute] GRPCRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.GRPCRoute] TLSRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.TLSRoute] + TCPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.TCPRoute] UDPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.UDPRoute] } @@ -54,6 +55,7 @@ func (p *ProviderResources) Close() { p.GatewayStatuses.Close() p.HTTPRouteStatuses.Close() p.TLSRouteStatuses.Close() + p.TCPRouteStatuses.Close() p.UDPRouteStatuses.Close() } diff --git a/internal/provider/kubernetes/config/rbac/role.yaml b/internal/provider/kubernetes/config/rbac/role.yaml index 5cb91cea5f6..51908e1b2a5 100644 --- a/internal/provider/kubernetes/config/rbac/role.yaml +++ b/internal/provider/kubernetes/config/rbac/role.yaml @@ -41,6 +41,7 @@ rules: - httproutes - referencegrants - referencepolicies + - tcproutes - tlsroutes - udproutes verbs: @@ -55,6 +56,7 @@ rules: - gateways/status - grpcroutes/status - httproutes/status + - tcproutes/status - tlsroutes/status - udproutes/status verbs: diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index ce3fff30f17..b80ddfb7581 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -40,12 +40,14 @@ const ( gatewayTLSRouteIndex = "gatewayTLSRouteIndex" gatewayHTTPRouteIndex = "gatewayHTTPRouteIndex" gatewayGRPCRouteIndex = "gatewayGRPCRouteIndex" + gatewayTCPRouteIndex = "gatewayTCPRouteIndex" gatewayUDPRouteIndex = "gatewayUDPRouteIndex" secretGatewayIndex = "secretGatewayIndex" targetRefGrantRouteIndex = "targetRefGrantRouteIndex" serviceHTTPRouteIndex = "serviceHTTPRouteIndex" serviceGRPCRouteIndex = "serviceGRPCRouteIndex" serviceTLSRouteIndex = "serviceTLSRouteIndex" + serviceTCPRouteIndex = "serviceTCPRouteIndex" serviceUDPRouteIndex = "serviceUDPRouteIndex" authenFilterHTTPRouteIndex = "authenHTTPRouteIndex" ) @@ -82,117 +84,10 @@ func newGatewayAPIController(mgr manager.Manager, cfg *config.Server, su status. // Subscribe to status updates r.subscribeAndUpdateStatus(ctx) - // Only enqueue GatewayClass objects that match this Envoy Gateway's controller name. - if err := c.Watch( - &source.Kind{Type: &gwapiv1b1.GatewayClass{}}, - &handler.EnqueueRequestForObject{}, - predicate.NewPredicateFuncs(r.hasMatchingController), - ); err != nil { - return err - } - - // Watch Gateway CRUDs and reconcile affected GatewayClass. - if err := c.Watch( - &source.Kind{Type: &gwapiv1b1.Gateway{}}, - &handler.EnqueueRequestForObject{}, - predicate.NewPredicateFuncs(r.validateGatewayForReconcile), - ); err != nil { - return err - } - if err := addGatewayIndexers(ctx, mgr); err != nil { - return err - } - - // Watch HTTPRoute CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &gwapiv1b1.HTTPRoute{}}, - &handler.EnqueueRequestForObject{}, - ); err != nil { - return err - } - if err := addHTTPRouteIndexers(ctx, mgr); err != nil { - return err - } - - // Watch GRPCRoute CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &gwapiv1a2.GRPCRoute{}}, - &handler.EnqueueRequestForObject{}, - ); err != nil { - return err - } - if err := addGRPCRouteIndexers(ctx, mgr); err != nil { - return err - } - - // Watch TLSRoute CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &gwapiv1a2.TLSRoute{}}, - &handler.EnqueueRequestForObject{}, - ); err != nil { - return err - } - if err := addTLSRouteIndexers(ctx, mgr); err != nil { - return err - } - - // Watch UDPRoute CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &gwapiv1a2.UDPRoute{}}, - &handler.EnqueueRequestForObject{}, - ); err != nil { - return err - } - if err := addUDPRouteIndexers(ctx, mgr); err != nil { + // watch gateway api resources + if err := r.watchResources(ctx, mgr, c); err != nil { return err } - - // Watch Service CRUDs and process affected *Route objects. - if err := c.Watch( - &source.Kind{Type: &corev1.Service{}}, - &handler.EnqueueRequestForObject{}, - predicate.NewPredicateFuncs(r.validateServiceForReconcile)); err != nil { - return err - } - - // Watch Secret CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &corev1.Secret{}}, - &handler.EnqueueRequestForObject{}, - predicate.NewPredicateFuncs(r.validateSecretForReconcile), - ); err != nil { - return err - } - - // Watch ReferenceGrant CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &gwapiv1a2.ReferenceGrant{}}, - &handler.EnqueueRequestForObject{}, - ); err != nil { - return err - } - if err := addReferenceGrantIndexers(ctx, mgr); err != nil { - return err - } - - // Watch Deployment CRUDs and process affected Gateways. - if err := c.Watch( - &source.Kind{Type: &appsv1.Deployment{}}, - &handler.EnqueueRequestForObject{}, - predicate.NewPredicateFuncs(r.validateDeploymentForReconcile), - ); err != nil { - return err - } - - // Watch AuthenticationFilter CRUDs and enqueue associated HTTPRoute objects. - if err := c.Watch( - &source.Kind{Type: &egv1a1.AuthenticationFilter{}}, - &handler.EnqueueRequestForObject{}, - predicate.NewPredicateFuncs(r.httpRoutesForAuthenticationFilter)); err != nil { - return err - } - - r.log.Info("watching gatewayAPI related objects") return nil } @@ -253,34 +148,9 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, request reconcile. return reconcile.Result{}, nil } - updater := func(gc *gwapiv1b1.GatewayClass, accepted bool) error { - if r.statusUpdater != nil { - r.statusUpdater.Send(status.Update{ - NamespacedName: types.NamespacedName{Name: gc.Name}, - Resource: &gwapiv1b1.GatewayClass{}, - Mutator: status.MutatorFunc(func(obj client.Object) client.Object { - gc, ok := obj.(*gwapiv1b1.GatewayClass) - if !ok { - panic(fmt.Sprintf("unsupported object type %T", obj)) - } - - return status.SetGatewayClassAccepted(gc.DeepCopy(), accepted) - }), - }) - } else { - // this branch makes testing easier by not going through the status.Updater. - copy := status.SetGatewayClassAccepted(gc.DeepCopy(), accepted) - - if err := r.client.Status().Update(ctx, copy); err != nil && !kerrors.IsNotFound(err) { - return fmt.Errorf("error updating status of gatewayclass %s: %w", copy.Name, err) - } - } - return nil - } - // Update status for all gateway classes for _, gc := range cc.notAcceptedClasses() { - if err := updater(gc, false); err != nil { + if err := r.gatewayClassUpdater(ctx, gc, false); err != nil { r.resources.GatewayAPIResources.Delete(acceptedGC.Name) return reconcile.Result{}, err } @@ -290,98 +160,10 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, request reconcile. resourceTree := gatewayapi.NewResources() resourceMap := newResourceMapping() - // Find gateways for the acceptedGC - // Find the Gateways that reference this Class. - gatewayList := &gwapiv1b1.GatewayList{} - if err := r.client.List(ctx, gatewayList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(classGatewayIndex, acceptedGC.Name), - }); err != nil { - r.log.Info("no associated Gateways found for GatewayClass", "name", acceptedGC.Name) + if err := r.processGateways(ctx, acceptedGC, resourceMap, resourceTree); err != nil { return reconcile.Result{}, err } - for _, gtw := range gatewayList.Items { - gtw := gtw - r.log.Info("processing Gateway", "namespace", gtw.Namespace, "name", gtw.Name) - resourceMap.allAssociatedNamespaces[gtw.Namespace] = struct{}{} - - for _, listener := range gtw.Spec.Listeners { - listener := listener - // Get Secret for gateway if it exists. - if terminatesTLS(&listener) { - for _, certRef := range listener.TLS.CertificateRefs { - certRef := certRef - if refsSecret(&certRef) { - secret := new(corev1.Secret) - secretNamespace := gatewayapi.NamespaceDerefOr(certRef.Namespace, gtw.Namespace) - err := r.client.Get(ctx, - types.NamespacedName{Namespace: secretNamespace, Name: string(certRef.Name)}, - secret, - ) - if err != nil && !kerrors.IsNotFound(err) { - r.log.Error(err, "unable to find Secret") - return reconcile.Result{}, err - } - - r.log.Info("processing Secret", "namespace", secretNamespace, "name", string(certRef.Name)) - - if secretNamespace != gtw.Namespace { - from := ObjectKindNamespacedName{ - kind: gatewayapi.KindGateway, - namespace: gtw.Namespace, - name: gtw.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindSecret, - namespace: secretNamespace, - name: string(certRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - // RefGrant found - resourceMap.allAssociatedRefGrants[utils.NamespacedName(refGrant)] = refGrant - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } - - resourceMap.allAssociatedNamespaces[secretNamespace] = struct{}{} - resourceTree.Secrets = append(resourceTree.Secrets, secret) - } - } - } - } - - // Route Processing - // Get TLSRoute objects and check if it exists. - if err := r.processTLSRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { - return reconcile.Result{}, err - } - - // Get HTTPRoute objects and check if it exists. - if err := r.processHTTPRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { - return reconcile.Result{}, err - } - - // Get GRPCRoute objects and check if it exists. - if err := r.processGRPCRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { - return reconcile.Result{}, err - } - - // Get UDPRoute objects and check if it exists. - if err := r.processUDPRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { - return reconcile.Result{}, err - } - - resourceTree.Gateways = append(resourceTree.Gateways, >w) - } - for serviceNamespaceName := range resourceMap.allAssociatedBackendRefs { r.log.Info("processing Service", "namespace", serviceNamespaceName.Namespace, "name", serviceNamespaceName.Name) @@ -426,7 +208,7 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, request reconcile. } } - if err := updater(acceptedGC, true); err != nil { + if err := r.gatewayClassUpdater(ctx, acceptedGC, true); err != nil { r.log.Error(err, "unable to update GatewayClass status") return reconcile.Result{}, err } @@ -459,6 +241,31 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, request reconcile. return reconcile.Result{}, nil } +func (r *gatewayAPIReconciler) gatewayClassUpdater(ctx context.Context, gc *gwapiv1b1.GatewayClass, accepted bool) error { + if r.statusUpdater != nil { + r.statusUpdater.Send(status.Update{ + NamespacedName: types.NamespacedName{Name: gc.Name}, + Resource: &gwapiv1b1.GatewayClass{}, + Mutator: status.MutatorFunc(func(obj client.Object) client.Object { + gc, ok := obj.(*gwapiv1b1.GatewayClass) + if !ok { + panic(fmt.Sprintf("unsupported object type %T", obj)) + } + + return status.SetGatewayClassAccepted(gc.DeepCopy(), accepted) + }), + }) + } else { + // this branch makes testing easier by not going through the status.Updater. + copy := status.SetGatewayClassAccepted(gc.DeepCopy(), accepted) + + if err := r.client.Status().Update(ctx, copy); err != nil && !kerrors.IsNotFound(err) { + return fmt.Errorf("error updating status of gatewayclass %s: %w", copy.Name, err) + } + } + return nil +} + func (r *gatewayAPIReconciler) getNamespace(ctx context.Context, name string) (*corev1.Namespace, error) { nsKey := types.NamespacedName{Name: name} ns := new(corev1.Namespace) @@ -535,6 +342,106 @@ func (r *gatewayAPIReconciler) getAuthenticationFilter(ctx context.Context, ns, return filter, nil } +func (r *gatewayAPIReconciler) processGateways(ctx context.Context, acceptedGC *gwapiv1b1.GatewayClass, resourceMap *resourceMappings, resourceTree *gatewayapi.Resources) error { + // Find gateways for the acceptedGC + // Find the Gateways that reference this Class. + gatewayList := &gwapiv1b1.GatewayList{} + if err := r.client.List(ctx, gatewayList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(classGatewayIndex, acceptedGC.Name), + }); err != nil { + r.log.Info("no associated Gateways found for GatewayClass", "name", acceptedGC.Name) + return err + } + + for _, gtw := range gatewayList.Items { + gtw := gtw + r.log.Info("processing Gateway", "namespace", gtw.Namespace, "name", gtw.Name) + resourceMap.allAssociatedNamespaces[gtw.Namespace] = struct{}{} + + for _, listener := range gtw.Spec.Listeners { + listener := listener + // Get Secret for gateway if it exists. + if terminatesTLS(&listener) { + for _, certRef := range listener.TLS.CertificateRefs { + certRef := certRef + if refsSecret(&certRef) { + secret := new(corev1.Secret) + secretNamespace := gatewayapi.NamespaceDerefOr(certRef.Namespace, gtw.Namespace) + err := r.client.Get(ctx, + types.NamespacedName{Namespace: secretNamespace, Name: string(certRef.Name)}, + secret, + ) + if err != nil && !kerrors.IsNotFound(err) { + r.log.Error(err, "unable to find Secret") + return err + } + + r.log.Info("processing Secret", "namespace", secretNamespace, "name", string(certRef.Name)) + + if secretNamespace != gtw.Namespace { + from := ObjectKindNamespacedName{ + kind: gatewayapi.KindGateway, + namespace: gtw.Namespace, + name: gtw.Name, + } + to := ObjectKindNamespacedName{ + kind: gatewayapi.KindSecret, + namespace: secretNamespace, + name: string(certRef.Name), + } + refGrant, err := r.findReferenceGrant(ctx, from, to) + switch { + case err != nil: + r.log.Error(err, "failed to find ReferenceGrant") + case refGrant == nil: + r.log.Info("no matching ReferenceGrants found", "from", from.kind, + "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) + default: + // RefGrant found + resourceMap.allAssociatedRefGrants[utils.NamespacedName(refGrant)] = refGrant + r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, + "name", refGrant.Name) + } + } + + resourceMap.allAssociatedNamespaces[secretNamespace] = struct{}{} + resourceTree.Secrets = append(resourceTree.Secrets, secret) + } + } + } + } + + // Route Processing + // Get TLSRoute objects and check if it exists. + if err := r.processTLSRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { + return err + } + + // Get HTTPRoute objects and check if it exists. + if err := r.processHTTPRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { + return err + } + + // Get GRPCRoute objects and check if it exists. + if err := r.processGRPCRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { + return err + } + + // Get TCPRoute objects and check if it exists. + if err := r.processTCPRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { + return err + } + + // Get UDPRoute objects and check if it exists. + if err := r.processUDPRoutes(ctx, utils.NamespacedName(>w).String(), resourceMap, resourceTree); err != nil { + return err + } + + resourceTree.Gateways = append(resourceTree.Gateways, >w) + } + return nil +} + func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error { if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.ReferenceGrant{}, targetRefGrantRouteIndex, func(rawObj client.Object) []string { refGrant := rawObj.(*gwapiv1a2.ReferenceGrant) @@ -731,6 +638,56 @@ func serviceTLSRouteIndexFunc(rawObj client.Object) []string { return services } +// addTCPRouteIndexers adds indexing on TCPRoute, for Service objects that are +// referenced in TCPRoute objects via `.spec.rules.backendRefs`. This helps in +// querying for TCPRoutes that are affected by a particular Service CRUD. +func addTCPRouteIndexers(ctx context.Context, mgr manager.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, func(rawObj client.Object) []string { + tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) + var gateways []string + for _, parent := range tcpRoute.Spec.ParentRefs { + if string(*parent.Kind) == gatewayapi.KindGateway { + // If an explicit Gateway namespace is not provided, use the TCPRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOrAlpha(parent.Namespace, tcpRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways + }); err != nil { + return err + } + + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TCPRoute{}, serviceTCPRouteIndex, serviceTCPRouteIndexFunc); err != nil { + return err + } + return nil +} + +func serviceTCPRouteIndexFunc(rawObj client.Object) []string { + tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) + var services []string + for _, rule := range tcpRoute.Spec.Rules { + for _, backend := range rule.BackendRefs { + if backend.Kind == nil || string(*backend.Kind) == gatewayapi.KindService { + // If an explicit Service namespace is not provided, use the TCPRoute namespace to + // lookup the provided Gateway Name. + services = append(services, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOrAlpha(backend.Namespace, tcpRoute.Namespace), + Name: string(backend.Name), + }.String(), + ) + } + } + } + return services +} + // addUDPRouteIndexers adds indexing on UDPRoute, for Service objects that are // referenced in UDPRoute objects via `.spec.rules.backendRefs`. This helps in // querying for UDPRoutes that are affected by a particular Service CRUD. @@ -986,6 +943,34 @@ func (r *gatewayAPIReconciler) subscribeAndUpdateStatus(ctx context.Context) { r.log.Info("tlsRoute status subscriber shutting down") }() + // TCPRoute object status updater + go func() { + message.HandleSubscription(r.resources.TCPRouteStatuses.Subscribe(ctx), + func(update message.Update[types.NamespacedName, *gwapiv1a2.TCPRoute]) { + // skip delete updates. + if update.Delete { + return + } + key := update.Key + val := update.Value + r.statusUpdater.Send(status.Update{ + NamespacedName: key, + Resource: new(gwapiv1a2.TCPRoute), + Mutator: status.MutatorFunc(func(obj client.Object) client.Object { + t, ok := obj.(*gwapiv1a2.TCPRoute) + if !ok { + panic(fmt.Sprintf("unsupported object type %T", obj)) + } + tCopy := t.DeepCopy() + tCopy.Status.Parents = val.Status.Parents + return tCopy + }), + }) + }, + ) + r.log.Info("tcpRoute status subscriber shutting down") + }() + // UDPRoute object status updater go func() { message.HandleSubscription(r.resources.UDPRouteStatuses.Subscribe(ctx), @@ -1014,3 +999,130 @@ func (r *gatewayAPIReconciler) subscribeAndUpdateStatus(ctx context.Context) { r.log.Info("udpRoute status subscriber shutting down") }() } + +// watchResources watches gateway api resources. +func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.Manager, c controller.Controller) error { + // Only enqueue GatewayClass objects that match this Envoy Gateway's controller name. + if err := c.Watch( + &source.Kind{Type: &gwapiv1b1.GatewayClass{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.hasMatchingController), + ); err != nil { + return err + } + + // Watch Gateway CRUDs and reconcile affected GatewayClass. + if err := c.Watch( + &source.Kind{Type: &gwapiv1b1.Gateway{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateGatewayForReconcile), + ); err != nil { + return err + } + if err := addGatewayIndexers(ctx, mgr); err != nil { + return err + } + + // Watch HTTPRoute CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &gwapiv1b1.HTTPRoute{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + if err := addHTTPRouteIndexers(ctx, mgr); err != nil { + return err + } + + // Watch GRPCRoute CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.GRPCRoute{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + if err := addGRPCRouteIndexers(ctx, mgr); err != nil { + return err + } + + // Watch TLSRoute CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.TLSRoute{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + if err := addTLSRouteIndexers(ctx, mgr); err != nil { + return err + } + + // Watch UDPRoute CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.UDPRoute{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + if err := addUDPRouteIndexers(ctx, mgr); err != nil { + return err + } + + // Watch TCPRoute CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.TCPRoute{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + if err := addTCPRouteIndexers(ctx, mgr); err != nil { + return err + } + + // Watch Service CRUDs and process affected *Route objects. + if err := c.Watch( + &source.Kind{Type: &corev1.Service{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateServiceForReconcile)); err != nil { + return err + } + + // Watch Secret CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &corev1.Secret{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateSecretForReconcile), + ); err != nil { + return err + } + + // Watch ReferenceGrant CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &gwapiv1a2.ReferenceGrant{}}, + &handler.EnqueueRequestForObject{}, + ); err != nil { + return err + } + if err := addReferenceGrantIndexers(ctx, mgr); err != nil { + return err + } + + // Watch Deployment CRUDs and process affected Gateways. + if err := c.Watch( + &source.Kind{Type: &appsv1.Deployment{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.validateDeploymentForReconcile), + ); err != nil { + return err + } + + // Watch AuthenticationFilter CRUDs and enqueue associated HTTPRoute objects. + if err := c.Watch( + &source.Kind{Type: &egv1a1.AuthenticationFilter{}}, + &handler.EnqueueRequestForObject{}, + predicate.NewPredicateFuncs(r.httpRoutesForAuthenticationFilter)); err != nil { + return err + } + + r.log.Info("watching gatewayAPI related objects") + return nil +} diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index 6881321b58e..85fcd096982 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -149,6 +149,14 @@ func (r *gatewayAPIReconciler) validateServiceForReconcile(obj client.Object) bo return false } + tcpRouteList := &gwapiv1a2.TCPRouteList{} + if err := r.client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(serviceTCPRouteIndex, utils.NamespacedName(svc).String()), + }); err != nil { + r.log.Error(err, "unable to find associated TCPRoutes") + return false + } + udpRouteList := &gwapiv1a2.UDPRouteList{} if err := r.client.List(ctx, udpRouteList, &client.ListOptions{ FieldSelector: fields.OneTermEqualSelector(serviceUDPRouteIndex, utils.NamespacedName(svc).String()), @@ -161,6 +169,7 @@ func (r *gatewayAPIReconciler) validateServiceForReconcile(obj client.Object) bo allAssociatedRoutes := len(httpRouteList.Items) + len(grpcRouteList.Items) + len(tlsRouteList.Items) + + len(tcpRouteList.Items) + len(udpRouteList.Items) return allAssociatedRoutes != 0 diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go index 9b2fadb202c..4864a03e5be 100644 --- a/internal/provider/kubernetes/predicates_test.go +++ b/internal/provider/kubernetes/predicates_test.go @@ -266,6 +266,17 @@ func TestValidateServiceForReconcile(t *testing.T) { service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil), expect: true, }, + { + name: "tcp route service routes exist", + configs: []client.Object{ + test.GetGatewayClass("test-gc", v1alpha1.GatewayControllerName), + sampleGateway, + test.GetTCPRoute(types.NamespacedName{Name: "tcproute-test"}, "scheduled-status-test", + types.NamespacedName{Name: "service"}), + }, + service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil), + expect: true, + }, } // Create the reconciler. @@ -284,6 +295,7 @@ func TestValidateServiceForReconcile(t *testing.T) { WithIndex(&gwapiv1b1.HTTPRoute{}, serviceHTTPRouteIndex, serviceHTTPRouteIndexFunc). WithIndex(&gwapiv1a2.GRPCRoute{}, serviceGRPCRouteIndex, serviceGRPCRouteIndexFunc). WithIndex(&gwapiv1a2.TLSRoute{}, serviceTLSRouteIndex, serviceTLSRouteIndexFunc). + WithIndex(&gwapiv1a2.TCPRoute{}, serviceTCPRouteIndex, serviceTCPRouteIndexFunc). WithIndex(&gwapiv1a2.UDPRoute{}, serviceUDPRouteIndex, serviceUDPRouteIndexFunc). Build() t.Run(tc.name, func(t *testing.T) { diff --git a/internal/provider/kubernetes/rbac.go b/internal/provider/kubernetes/rbac.go index c8f28408567..a7ce8f5bef3 100644 --- a/internal/provider/kubernetes/rbac.go +++ b/internal/provider/kubernetes/rbac.go @@ -5,8 +5,8 @@ package kubernetes -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;grpcroutes;tlsroutes;udproutes;referencepolicies;referencegrants,verbs=get;list;watch;update -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status;grpcroutes/status;tlsroutes/status;udproutes/status,verbs=update +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;grpcroutes;tlsroutes;tcproutes;udproutes;referencepolicies;referencegrants,verbs=get;list;watch;update +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status;grpcroutes/status;tlsroutes/status;tcproutes/status;udproutes/status,verbs=update // +kubebuilder:rbac:groups="gateway.envoyproxy.io",resources=authenticationfilters,verbs=get;list;watch;update // RBAC for watched resources of Gateway API controllers. diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go index 8c36e7622a9..1cc480403ad 100644 --- a/internal/provider/kubernetes/routes.go +++ b/internal/provider/kubernetes/routes.go @@ -233,6 +233,63 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam return nil } +// processTCPRoutes finds TCPRoutes corresponding to a gatewayNamespaceName, further checks for +// the backend references and pushes the TCPRoutes to the resourceTree. +func (r *gatewayAPIReconciler) processTCPRoutes(ctx context.Context, gatewayNamespaceName string, + resourceMap *resourceMappings, resourceTree *gatewayapi.Resources) error { + tcpRouteList := &gwapiv1a2.TCPRouteList{} + if err := r.client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(gatewayTCPRouteIndex, gatewayNamespaceName), + }); err != nil { + r.log.Error(err, "unable to find associated UDPRoutes") + return err + } + + for _, tcpRoute := range tcpRouteList.Items { + tcpRoute := tcpRoute + r.log.Info("processing TCPRoute", "namespace", tcpRoute.Namespace, "name", tcpRoute.Name) + + for _, rule := range tcpRoute.Spec.Rules { + for _, backendRef := range rule.BackendRefs { + backendRef := backendRef + ref := gatewayapi.UpgradeBackendRef(backendRef) + if err := validateBackendRef(&ref); err != nil { + r.log.Error(err, "invalid backendRef") + continue + } + + backendNamespace := gatewayapi.NamespaceDerefOrAlpha(backendRef.Namespace, tcpRoute.Namespace) + resourceMap.allAssociatedBackendRefs[types.NamespacedName{ + Namespace: backendNamespace, + Name: string(backendRef.Name), + }] = struct{}{} + + if backendNamespace != tcpRoute.Namespace { + from := ObjectKindNamespacedName{kind: gatewayapi.KindTCPRoute, namespace: tcpRoute.Namespace, name: tcpRoute.Name} + to := ObjectKindNamespacedName{kind: gatewayapi.KindService, namespace: backendNamespace, name: string(backendRef.Name)} + refGrant, err := r.findReferenceGrant(ctx, from, to) + switch { + case err != nil: + r.log.Error(err, "failed to find ReferenceGrant") + case refGrant == nil: + r.log.Info("no matching ReferenceGrants found", "from", from.kind, + "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) + default: + resourceMap.allAssociatedRefGrants[utils.NamespacedName(refGrant)] = refGrant + r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, + "name", refGrant.Name) + } + } + } + } + + resourceMap.allAssociatedNamespaces[tcpRoute.Namespace] = struct{}{} + resourceTree.TCPRoutes = append(resourceTree.TCPRoutes, &tcpRoute) + } + + return nil +} + // processUDPRoutes finds UDPRoutes corresponding to a gatewayNamespaceName, further checks for // the backend references and pushes the UDPRoutes to the resourceTree. func (r *gatewayAPIReconciler) processUDPRoutes(ctx context.Context, gatewayNamespaceName string, diff --git a/internal/provider/kubernetes/test/utils.go b/internal/provider/kubernetes/test/utils.go index 15a8f91f523..2f4610df0fa 100644 --- a/internal/provider/kubernetes/test/utils.go +++ b/internal/provider/kubernetes/test/utils.go @@ -168,6 +168,34 @@ func GetTLSRoute(nsName types.NamespacedName, parent string, serviceName types.N } } +// GetTCPRoute returns a sample TCPRoute with a parent reference. +func GetTCPRoute(nsName types.NamespacedName, parent string, serviceName types.NamespacedName) *gwapiv1a2.TCPRoute { + return &gwapiv1a2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: nsName.Namespace, + Name: nsName.Name, + }, + Spec: gwapiv1a2.TCPRouteSpec{ + CommonRouteSpec: gwapiv1a2.CommonRouteSpec{ + ParentRefs: []gwapiv1a2.ParentReference{ + {Name: gwapiv1a2.ObjectName(parent)}, + }, + }, + Rules: []gwapiv1a2.TCPRouteRule{ + { + BackendRefs: []gwapiv1a2.BackendRef{ + { + BackendObjectReference: gwapiv1a2.BackendObjectReference{ + Name: gwapiv1a2.ObjectName(serviceName.Name), + }, + }, + }, + }, + }, + }, + } +} + // GetUDPRoute returns a sample UDPRoute with a parent reference. func GetUDPRoute(nsName types.NamespacedName, parent string, serviceName types.NamespacedName) *gwapiv1a2.UDPRoute { return &gwapiv1a2.UDPRoute{ diff --git a/internal/provider/kubernetes/testdata/in/tcproute-experimental-crd.yaml b/internal/provider/kubernetes/testdata/in/tcproute-experimental-crd.yaml new file mode 100644 index 00000000000..9924e0ab334 --- /dev/null +++ b/internal/provider/kubernetes/testdata/in/tcproute-experimental-crd.yaml @@ -0,0 +1,518 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined + with a Gateway listener, it can be used to forward connections on the port + specified by the listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) + that a Route wants to be attached to. Note that the referenced parent + resource needs to allow this for the attachment to be complete. + For Gateways, that means the Gateway needs to allow attachment from + Routes of this kind and namespace. \n The only kind of parent resource + with \"Core\" support is Gateway. This API may be extended in the + future to support additional kinds of parent resources such as one + of the route kinds. \n It is invalid to reference an identical parent + more than once. It is valid to reference multiple distinct sections + within the same parent resource, such as 2 Listeners within a Gateway. + \n It is possible to separately reference multiple distinct objects + that may be collapsed by an implementation. For example, some implementations + may choose to merge compatible Gateway Listeners together. If that + is the case, the list of routes attached to those resources should + also be merged. \n Note that for ParentRefs that cross namespace + boundaries, there are specific rules. Cross-namespace references + are only valid if they are explicitly allowed by something in the + namespace they are referring to. For example, Gateway has the AllowedRoutes + field, and ReferenceGrant provides a generic way to enable any other + kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually + a Gateway) that can be considered a parent of this resource (usually + a route). The only kind of parent resource with \"Core\" support + is Gateway. This API may be extended in the future to support + additional kinds of parent resources, such as HTTPRoute. \n The + API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the core + API group (such as for a \"Service\" kind referent), Group + must be explicitly set to \"\" (empty string). \n Support: + Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core + (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When + unspecified, this refers to the local namespace of the Route. + \n Note that there are specific rules for ParentRefs which + cross namespace boundaries. Cross-namespace references are + only valid if they are explicitly allowed by something in + the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It + can be interpreted differently based on the type of parent + resource. \n When the parent resource is a Gateway, this targets + all listeners listening on the specified port that also support + this kind of Route(and select this Route). It's not recommended + to set `Port` unless the networking behaviors specified in + a Route must apply to a specific port as opposed to a listener(s) + whose port(s) may be changed. When both Port and SectionName + are specified, the name and port of the selected listener + must match both specified values. \n Implementations MAY choose + to support other parent resources. Implementations supporting + other types of parent resources MUST clearly document how/if + Port is interpreted. \n For the purpose of status, an attachment + is considered successful as long as the parent resource accepts + it partially. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the + target resource. In the following resources, SectionName is + interpreted as the following: \n * Gateway: Listener Name. + When both Port (experimental) and SectionName are specified, + the name and port of the selected listener must match both + specified values. \n Implementations MAY choose to support + attaching Routes to other resources. If that is the case, + they MUST clearly document how SectionName is interpreted. + \n When unspecified (empty string), this will reference the + entire resource. For the purpose of status, an attachment + is considered successful if at least one section in the parent + resource accepts it. For example, Gateway listeners can restrict + which Routes can attach to them by Route kind, namespace, + or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this + Route, the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching + requests should be sent. If unspecified or invalid (refers + to a non-existent resource or a Service with no endpoints), + the underlying implementation MUST actively reject connection + attempts to this backend. Connection rejections must respect + weight; if an invalid backend is requested to have 80% of + connections, then 80% of connections must be rejected instead. + \n Support: Core for Kubernetes Service \n Support: Implementation-specific + for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward + a request to a Kubernetes resource. \n Note that when a + namespace is specified, a ReferenceGrant object is required + in the referent namespace to allow that namespace's owner + to accept the reference. See the ReferenceGrant documentation + for details." + properties: + group: + default: "" + description: Group is the group of the referent. For example, + "gateway.networking.k8s.io". When unspecified or empty + string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". Defaults to "Service" when + not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. + When unspecified, the local namespace is inferred. \n + Note that when a namespace is specified, a ReferenceGrant + object is required in the referent namespace to allow + that namespace's owner to accept the reference. See + the ReferenceGrant documentation for details. \n Support: + Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number + to use for this resource. Port is required when the + referent is a Kubernetes Service. In this case, the + port number is the service port number, not the target + port. For other resources, destination port might be + derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests + forwarded to the referenced backend. This is computed + as weight/(sum of all weights in this BackendRefs list). + For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision + an implementation supports. Weight is not a percentage + and the sum of weights does not need to equal 100. \n + If only one backend is specified and it has a weight + greater than 0, 100% of the traffic is forwarded to + that backend. If weight is set to 0, no traffic should + be forwarded for this entry. If unspecified, weight + defaults to 1. \n Support for this field varies based + on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) + that are associated with the route, and the status of the route + with respect to each parent. When this route attaches to a parent, + the controller that manages the parent must add an entry to this + list when the controller first sees the route and should update + the entry as appropriate when the route or gateway is modified. + \n Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this + API can only populate Route status for the Gateways/parent resources + they are responsible for. \n A maximum of 32 Gateways will be represented + in this list. An empty list means the route has not been attached + to any Gateway." + items: + description: RouteParentStatus describes the status of a route with + respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with + respect to the Gateway. Note that the route's availability + is also subject to the Gateway's own status conditions and + listener status. \n If the Route's ParentRef specifies an + existing Gateway that supports Routes of this kind AND that + Gateway's controller has sufficient access, then that Gateway's + controller MUST set the \"Accepted\" condition on the Route, + to indicate whether the route has been accepted or rejected + by the Gateway, and why. \n A Route MUST be considered \"Accepted\" + if at least one of the Route's rules is implemented by the + Gateway. \n There are a number of cases where the \"Accepted\" + condition may not be set due to lack of controller visibility, + that includes when: \n * The Route refers to a non-existent + parent. * The Route is of a type that the controller does + not support. * The Route is in a namespace the controller + does not have access to." + items: + description: "Condition contains details for one aspect of + the current state of this API Resource. --- This struct + is intended for direct use as an array at the field path + .status.conditions. For example, \n \ttype FooStatus struct{ + \t // Represents the observations of a foo's current + state. \t // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type + \t // +patchStrategy=merge \t // +listType=map \t + \ // +listMapKey=type \t Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other + fields \t}" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should + be when the underlying condition changed. If that is + not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, + if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the + current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier + indicating the reason for the condition's last transition. + Producers of specific condition types may define expected + values and meanings for this field, and whether the + values are considered a guaranteed API. The value should + be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across + resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability + to deconflict is important. The regex it matches is + (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates + the name of the controller that wrote this status. This corresponds + with the controllerName field on GatewayClass. \n Example: + \"example.net/gateway-controller\". \n The format of this + field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid + Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + \n Controllers MUST populate this field when writing status. + Controllers should ensure that entries to status populated + with their ControllerName are cleaned up when they are no + longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec + that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, + \"gateway.networking.k8s.io\" is inferred. To set the + core API group (such as for a \"Service\" kind referent), + Group must be explicitly set to \"\" (empty string). \n + Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: + Core (Gateway) \n Support: Implementation-specific (Other + Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: + Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. + When unspecified, this refers to the local namespace of + the Route. \n Note that there are specific rules for ParentRefs + which cross namespace boundaries. Cross-namespace references + are only valid if they are explicitly allowed by something + in the namespace they are referring to. For example: Gateway + has the AllowedRoutes field, and ReferenceGrant provides + a generic way to enable any other kind of cross-namespace + reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. + It can be interpreted differently based on the type of + parent resource. \n When the parent resource is a Gateway, + this targets all listeners listening on the specified + port that also support this kind of Route(and select this + Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to + a specific port as opposed to a listener(s) whose port(s) + may be changed. When both Port and SectionName are specified, + the name and port of the selected listener must match + both specified values. \n Implementations MAY choose to + support other parent resources. Implementations supporting + other types of parent resources MUST clearly document + how/if Port is interpreted. \n For the purpose of status, + an attachment is considered successful as long as the + parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them + by Route kind, namespace, or hostname. If 1 of 2 Gateway + listeners accept attachment from the referencing Route, + the Route MUST be considered successfully attached. If + no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within + the target resource. In the following resources, SectionName + is interpreted as the following: \n * Gateway: Listener + Name. When both Port (experimental) and SectionName are + specified, the name and port of the selected listener + must match both specified values. \n Implementations MAY + choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName + is interpreted. \n When unspecified (empty string), this + will reference the entire resource. For the purpose of + status, an attachment is considered successful if at least + one section in the parent resource accepts it. For example, + Gateway listeners can restrict which Routes can attach + to them by Route kind, namespace, or hostname. If 1 of + 2 Gateway listeners accept attachment from the referencing + Route, the Route MUST be considered successfully attached. + If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 957e94607ad..ce3d35e7b04 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -19,6 +19,7 @@ import ( tcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" udp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/udp/udp_proxy/v3" tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "google.golang.org/protobuf/types/known/anypb" @@ -359,3 +360,22 @@ func buildXdsUDPListener(clusterName string, udpListener *ir.UDPListener) (*list return xdsListener, nil } + +// Point to xds cluster. +func makeConfigSource() *core.ConfigSource { + source := &core.ConfigSource{} + source.ResourceApiVersion = resource.DefaultAPIVersion + source.ConfigSourceSpecifier = &core.ConfigSource_ApiConfigSource{ + ApiConfigSource: &core.ApiConfigSource{ + TransportApiVersion: resource.DefaultAPIVersion, + ApiType: core.ApiConfigSource_DELTA_GRPC, + SetNodeOnFirstMessageOnly: true, + GrpcServices: []*core.GrpcService{{ + TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "xds_cluster"}, + }, + }}, + }, + } + return source +} diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend.yaml new file mode 100644 index 00000000000..e6d314d1f5e --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/http-route-weighted-backend.yaml @@ -0,0 +1,21 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + routes: + - name: "first-route" + destinations: + - host: "1.1.1.1" + port: 50001 + weight: 20 + - host: "2.2.2.2" + port: 50002 + weight: 40 + - host: "3.3.3.3" + port: 50003 + weight: 20 + - host: "4.4.4.4" + port: 50004 + weight: 20 diff --git a/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml b/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml new file mode 100644 index 00000000000..324de212838 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml @@ -0,0 +1,41 @@ +tcp: +- name: "tcp-route-simple" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 +- name: "tcp-route-simple-1" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 +- name: "tcp-route-simple-2" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 +- name: "tcp-route-simple-3" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 +- name: "tcp-route-simple-4" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml new file mode 100644 index 00000000000..1de28530c7d --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml @@ -0,0 +1,14 @@ +tcp: +- name: "tcp-route-complex" + address: "0.0.0.0" + port: 10080 + tls: + snis: + - foo.com + - bar.com + - example.com + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml new file mode 100644 index 00000000000..b88d57dd399 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml @@ -0,0 +1,9 @@ +tcp: +- name: "tcp-route-simple" + address: "0.0.0.0" + port: 10080 + destinations: + - host: "1.2.3.4" + port: 50000 + - host: "5.6.7.8" + port: 50001 diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml new file mode 100644 index 00000000000..fe02dec82c7 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml @@ -0,0 +1,22 @@ +tcp: +- name: "tcp-route-weighted-backend" + address: "0.0.0.0" + port: 10080 + tls: + snis: + - foo.com + - bar.com + - example.com + destinations: + - host: "1.1.1.1" + port: 50001 + weight: 20 + - host: "2.2.2.2" + port: 50002 + weight: 40 + - host: "3.3.3.3" + port: 50003 + weight: 20 + - host: "4.4.4.4" + port: 50004 + weight: 20 diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.clusters.yaml new file mode 100644 index 00000000000..bb307d77082 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.clusters.yaml @@ -0,0 +1,37 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: first-route + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.1.1.1 + portValue: 50001 + loadBalancingWeight: 20 + - endpoint: + address: + socketAddress: + address: 2.2.2.2 + portValue: 50002 + loadBalancingWeight: 40 + - endpoint: + address: + socketAddress: + address: 3.3.3.3 + portValue: 50003 + loadBalancingWeight: 20 + - endpoint: + address: + socketAddress: + address: 4.4.4.4 + portValue: 50004 + loadBalancingWeight: 20 + loadBalancingWeight: 1 + locality: {} + name: first-route + outlierDetection: {} + type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.listeners.yaml new file mode 100644 index 00000000000..6c555573979 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.listeners.yaml @@ -0,0 +1,40 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + rds: + configSource: + apiConfigSource: + apiType: DELTA_GRPC + grpcServices: + - envoyGrpc: + clusterName: xds_cluster + setNodeOnFirstMessageOnly: true + transportApiVersion: V3 + resourceApiVersion: V3 + routeConfigName: first-listener + statPrefix: http + name: first-listener diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.routes.yaml new file mode 100644 index 00000000000..ed122e552aa --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-backend.routes.yaml @@ -0,0 +1,10 @@ +- name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener + routes: + - match: + prefix: / + route: + cluster: first-route diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.clusters.yaml new file mode 100644 index 00000000000..9b23924af5a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.clusters.yaml @@ -0,0 +1,115 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-simple + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-simple + outlierDetection: {} + type: STATIC +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-simple-1 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-simple-1 + outlierDetection: {} + type: STATIC +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-simple-2 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-simple-2 + outlierDetection: {} + type: STATIC +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-simple-3 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-simple-3 + outlierDetection: {} + type: STATIC +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-simple-4 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-simple-4 + outlierDetection: {} + type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml new file mode 100644 index 00000000000..128fd616fca --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml @@ -0,0 +1,70 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-simple + statPrefix: tcp + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-simple-1 + statPrefix: tcp + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-simple-2 + statPrefix: tcp + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-simple-3 + statPrefix: tcp + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-simple-4 + statPrefix: tcp + name: tcp-route-simple diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.routes.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.clusters.yaml new file mode 100644 index 00000000000..eb1b767d85c --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.clusters.yaml @@ -0,0 +1,23 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-complex + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-complex + outlierDetection: {} + type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml new file mode 100644 index 00000000000..dfb2a30acf0 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml @@ -0,0 +1,35 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filterChainMatch: + serverNames: + - foo.com + - bar.com + - example.com + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-complex + statPrefix: passthrough + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + name: tcp-route-complex diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.routes.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.clusters.yaml new file mode 100644 index 00000000000..54c0cd9b4b3 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.clusters.yaml @@ -0,0 +1,23 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-simple + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + - endpoint: + address: + socketAddress: + address: 5.6.7.8 + portValue: 50001 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-simple + outlierDetection: {} + type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml new file mode 100644 index 00000000000..05e159719fb --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml @@ -0,0 +1,26 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-simple + statPrefix: tcp + name: tcp-route-simple diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.routes.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.clusters.yaml new file mode 100644 index 00000000000..c24165c14ea --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.clusters.yaml @@ -0,0 +1,37 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 5s + dnsLookupFamily: V4_ONLY + loadAssignment: + clusterName: tcp-route-weighted-backend + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.1.1.1 + portValue: 50001 + loadBalancingWeight: 20 + - endpoint: + address: + socketAddress: + address: 2.2.2.2 + portValue: 50002 + loadBalancingWeight: 40 + - endpoint: + address: + socketAddress: + address: 3.3.3.3 + portValue: 50003 + loadBalancingWeight: 20 + - endpoint: + address: + socketAddress: + address: 4.4.4.4 + portValue: 50004 + loadBalancingWeight: 20 + loadBalancingWeight: 1 + locality: {} + name: tcp-route-weighted-backend + outlierDetection: {} + type: STATIC diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml new file mode 100644 index 00000000000..e16c3a23849 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml @@ -0,0 +1,35 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filterChainMatch: + serverNames: + - foo.com + - bar.com + - example.com + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tcp-route-weighted-backend + statPrefix: passthrough + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + name: tcp-route-weighted-backend diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.routes.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 78f0b241fa7..a3bb2795df4 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -26,7 +26,23 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { tCtx := new(types.ResourceVersionTable) - for _, httpListener := range ir.HTTP { + if err := processHTTPListenerXdsTranslation(tCtx, ir.HTTP); err != nil { + return nil, err + } + + if err := processTCPListenerXdsTranslation(tCtx, ir.TCP); err != nil { + return nil, err + } + + if err := processUDPListenerXdsTranslation(tCtx, ir.UDP); err != nil { + return nil, err + } + + return tCtx, nil +} + +func processHTTPListenerXdsTranslation(tCtx *types.ResourceVersionTable, httpListeners []*ir.HTTPListener) error { + for _, httpListener := range httpListeners { addFilterChain := true var xdsRouteCfg *route.RouteConfiguration @@ -46,14 +62,14 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { addFilterChain = false xdsRouteCfg = findXdsRouteConfig(tCtx, routeName) if xdsRouteCfg == nil { - return nil, errors.New("unable to find xds route config") + return errors.New("unable to find xds route config") } } } if addFilterChain { if err := addXdsHTTPFilterChain(xdsListener, httpListener); err != nil { - return nil, err + return err } } @@ -89,13 +105,15 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { } xdsCluster := buildXdsCluster(httpRoute.Name, httpRoute.Destinations, httpListener.IsHTTP2) tCtx.AddXdsResource(resource.ClusterType, xdsCluster) - } xdsRouteCfg.VirtualHosts = append(xdsRouteCfg.VirtualHosts, vHost) } + return nil +} - for _, tcpListener := range ir.TCP { +func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListeners []*ir.TCPListener) error { + for _, tcpListener := range tcpListeners { // 1:1 between IR TCPListener and xDS Cluster xdsCluster := buildXdsCluster(tcpListener.Name, tcpListener.Destinations, false /*isHTTP2 */) tCtx.AddXdsResource(resource.ClusterType, xdsCluster) @@ -108,11 +126,14 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { } if err := addXdsTCPFilterChain(xdsListener, tcpListener, xdsCluster.Name); err != nil { - return nil, err + return err } } + return nil +} - for _, udpListener := range ir.UDP { +func processUDPListenerXdsTranslation(tCtx *types.ResourceVersionTable, udpListeners []*ir.UDPListener) error { + for _, udpListener := range udpListeners { // 1:1 between IR UDPListener and xDS Cluster xdsCluster := buildXdsCluster(udpListener.Name, udpListener.Destinations, false /*isHTTP2 */) tCtx.AddXdsResource(resource.ClusterType, xdsCluster) @@ -121,11 +142,11 @@ func Translate(ir *ir.Xds) (*types.ResourceVersionTable, error) { // translator xdsListener, err := buildXdsUDPListener(xdsCluster.Name, udpListener) if err != nil { - return nil, multierror.Append(err, errors.New("error building xds cluster")) + return multierror.Append(err, errors.New("error building xds cluster")) } tCtx.AddXdsResource(resource.ListenerType, xdsListener) } - return tCtx, nil + return nil } // findXdsListener finds a xds listener with the same address, port and protocol, and returns nil if there is no match. @@ -162,22 +183,3 @@ func findXdsRouteConfig(tCtx *types.ResourceVersionTable, name string) *route.Ro return nil } - -// Point to xds cluster. -func makeConfigSource() *core.ConfigSource { - source := &core.ConfigSource{} - source.ResourceApiVersion = resource.DefaultAPIVersion - source.ConfigSourceSpecifier = &core.ConfigSource_ApiConfigSource{ - ApiConfigSource: &core.ApiConfigSource{ - TransportApiVersion: resource.DefaultAPIVersion, - ApiType: core.ApiConfigSource_DELTA_GRPC, - SetNodeOnFirstMessageOnly: true, - GrpcServices: []*core.GrpcService{{ - TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ - EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "xds_cluster"}, - }, - }}, - }, - } - return source -} diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index a785ee4e6b3..3d8df224534 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -67,6 +67,21 @@ func TestTranslate(t *testing.T) { { name: "tls-route-passthrough", }, + { + name: "tcp-route-simple", + }, + { + name: "tcp-route-complex", + }, + { + name: "multiple-simple-tcp-route-same-port", + }, + { + name: "http-route-weighted-backend", + }, + { + name: "tcp-route-weighted-backend", + }, { name: "multiple-listeners-same-port", requireSecrets: true,