diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index 9e861692..c8213c26 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -47,8 +47,11 @@ type BackendTrafficPolicySpec struct {
LoadBalancer *LoadBalancer `json:"loadbalancer,omitempty" yaml:"loadbalancer,omitempty"`
// Scheme is the protocol used to communicate with the upstream.
// Default is `http`.
- // Can be `http`, `https`, `grpc`, or `grpcs`.
- // +kubebuilder:validation:Enum=http;https;grpc;grpcs;
+ // For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`.
+ // For L4 proxy, it can be `tcp`, `tls`, or `udp`.
+ // The L4 values apply to stream routes only; using them for an HTTP route
+ // makes the upstream unreachable.
+ // +kubebuilder:validation:Enum=http;https;grpc;grpcs;tcp;tls;udp;
// +kubebuilder:default=http
Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
diff --git a/api/v2/apisixupstream_types.go b/api/v2/apisixupstream_types.go
index 883c509f..e08ece26 100644
--- a/api/v2/apisixupstream_types.go
+++ b/api/v2/apisixupstream_types.go
@@ -106,9 +106,12 @@ type ApisixUpstreamConfig struct {
// Scheme is the protocol used to communicate with the upstream.
// Default is `http`.
- // Can be `http`, `https`, `grpc`, or `grpcs`.
+ // For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`.
+ // For L4 proxy, it can be `tcp`, `tls`, or `udp`.
+ // The L4 values apply to stream routes only; using them for an HTTP route
+ // makes the upstream unreachable.
// +kubebuilder:validation:Optional
- // +kubebuilder:validation:Enum=http;https;grpc;grpcs;
+ // +kubebuilder:validation:Enum=http;https;grpc;grpcs;tcp;tls;udp;
Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
// Retries defines the number of retry attempts APISIX should make when a failure occurs.
diff --git a/config/crd/bases/apisix.apache.org_apisixupstreams.yaml b/config/crd/bases/apisix.apache.org_apisixupstreams.yaml
index a3a08cbd..2e572d01 100644
--- a/config/crd/bases/apisix.apache.org_apisixupstreams.yaml
+++ b/config/crd/bases/apisix.apache.org_apisixupstreams.yaml
@@ -591,12 +591,18 @@ spec:
description: |-
Scheme is the protocol used to communicate with the upstream.
Default is `http`.
- Can be `http`, `https`, `grpc`, or `grpcs`.
+ For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`.
+ For L4 proxy, it can be `tcp`, `tls`, or `udp`.
+ The L4 values apply to stream routes only; using them for an HTTP route
+ makes the upstream unreachable.
enum:
- http
- https
- grpc
- grpcs
+ - tcp
+ - tls
+ - udp
type: string
subsets:
description: |-
@@ -670,12 +676,18 @@ spec:
description: |-
Scheme is the protocol used to communicate with the upstream.
Default is `http`.
- Can be `http`, `https`, `grpc`, or `grpcs`.
+ For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`.
+ For L4 proxy, it can be `tcp`, `tls`, or `udp`.
+ The L4 values apply to stream routes only; using them for an HTTP route
+ makes the upstream unreachable.
enum:
- http
- https
- grpc
- grpcs
+ - tcp
+ - tls
+ - udp
type: string
subsets:
description: |-
diff --git a/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml b/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml
index 0ea77069..b5af1854 100644
--- a/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml
+++ b/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml
@@ -284,12 +284,18 @@ spec:
description: |-
Scheme is the protocol used to communicate with the upstream.
Default is `http`.
- Can be `http`, `https`, `grpc`, or `grpcs`.
+ For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`.
+ For L4 proxy, it can be `tcp`, `tls`, or `udp`.
+ The L4 values apply to stream routes only; using them for an HTTP route
+ makes the upstream unreachable.
enum:
- http
- https
- grpc
- grpcs
+ - tcp
+ - tls
+ - udp
type: string
targetRefs:
description: |-
diff --git a/docs/en/latest/reference/api-reference.md b/docs/en/latest/reference/api-reference.md
index 25b729c2..8def827e 100644
--- a/docs/en/latest/reference/api-reference.md
+++ b/docs/en/latest/reference/api-reference.md
@@ -254,7 +254,7 @@ _Appears in:_
| --- | --- |
| `targetRefs` _[BackendPolicyTargetReferenceWithSectionName](#backendpolicytargetreferencewithsectionname) array_ | TargetRef identifies an API object to apply policy to. Currently, Backends (i.e. Service, ServiceImport, or any implementation-specific backendRef) are the only valid API target references. |
| `loadbalancer` _[LoadBalancer](#loadbalancer)_ | LoadBalancer represents the load balancer configuration for Kubernetes Service. The default strategy is round robin. |
-| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
+| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`. For L4 proxy, it can be `tcp`, `tls`, or `udp`. The L4 values apply to stream routes only; using them for an HTTP route makes the upstream unreachable. |
| `retries` _integer_ | Retries specify the number of times the gateway should retry sending requests when errors such as timeouts or 502 errors occur. |
| `timeout` _[Timeout](#timeout)_ | Timeout sets the read, send, and connect timeouts to the upstream. |
| `passHost` _string_ | PassHost configures how the host header should be determined when a request is forwarded to the upstream. Default is `pass`. Can be `pass`, `node` or `rewrite`:
• `pass`: preserve the original Host header
• `node`: use the upstream node’s host
• `rewrite`: set to a custom host via `upstreamHost` |
@@ -1489,7 +1489,7 @@ ApisixUpstreamConfig defines configuration for upstream services.
| Field | Description |
| --- | --- |
| `loadbalancer` _[LoadBalancer](#loadbalancer)_ | LoadBalancer specifies the load balancer configuration for Kubernetes Service. |
-| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
+| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`. For L4 proxy, it can be `tcp`, `tls`, or `udp`. The L4 values apply to stream routes only; using them for an HTTP route makes the upstream unreachable. |
| `retries` _integer_ | Retries defines the number of retry attempts APISIX should make when a failure occurs. Failures include timeouts, network errors, or 5xx status codes. |
| `timeout` _[UpstreamTimeout](#upstreamtimeout)_ | Timeout specifies the connection, send, and read timeouts for upstream requests. |
| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active and passive health check configuration for the upstream. |
@@ -1549,7 +1549,7 @@ definitions and custom configuration.
| `ingressClassName` _string_ | IngressClassName is the name of an IngressClass cluster resource. Controller implementations use this field to determine whether they should process this ApisixUpstream resource. |
| `externalNodes` _[ApisixUpstreamExternalNode](#apisixupstreamexternalnode) array_ | ExternalNodes defines a static list of backend nodes. These can be external hosts outside the cluster or cluster-internal Services specified by their DNS name. When this field is set, the upstream will route traffic directly to these nodes without DNS resolution or service discovery. |
| `loadbalancer` _[LoadBalancer](#loadbalancer)_ | LoadBalancer specifies the load balancer configuration for Kubernetes Service. |
-| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
+| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`. For L4 proxy, it can be `tcp`, `tls`, or `udp`. The L4 values apply to stream routes only; using them for an HTTP route makes the upstream unreachable. |
| `retries` _integer_ | Retries defines the number of retry attempts APISIX should make when a failure occurs. Failures include timeouts, network errors, or 5xx status codes. |
| `timeout` _[UpstreamTimeout](#upstreamtimeout)_ | Timeout specifies the connection, send, and read timeouts for upstream requests. |
| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active and passive health check configuration for the upstream. |
@@ -1719,7 +1719,7 @@ them if they are set on the port level.
| Field | Description |
| --- | --- |
| `loadbalancer` _[LoadBalancer](#loadbalancer)_ | LoadBalancer specifies the load balancer configuration for Kubernetes Service. |
-| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
+| `scheme` _string_ | Scheme is the protocol used to communicate with the upstream. Default is `http`. For L7 proxy, it can be `http`, `https`, `grpc`, or `grpcs`. For L4 proxy, it can be `tcp`, `tls`, or `udp`. The L4 values apply to stream routes only; using them for an HTTP route makes the upstream unreachable. |
| `retries` _integer_ | Retries defines the number of retry attempts APISIX should make when a failure occurs. Failures include timeouts, network errors, or 5xx status codes. |
| `timeout` _[UpstreamTimeout](#upstreamtimeout)_ | Timeout specifies the connection, send, and read timeouts for upstream requests. |
| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active and passive health check configuration for the upstream. |
diff --git a/internal/adc/translator/apisixroute_test.go b/internal/adc/translator/apisixroute_test.go
index 6ac355eb..89a80007 100644
--- a/internal/adc/translator/apisixroute_test.go
+++ b/internal/adc/translator/apisixroute_test.go
@@ -18,14 +18,22 @@
package translator
import (
+ "context"
"testing"
"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ corev1 "k8s.io/api/core/v1"
+ discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ k8stypes "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/intstr"
+ "k8s.io/utils/ptr"
adc "github.com/apache/apisix-ingress-controller/api/adc"
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+ "github.com/apache/apisix-ingress-controller/internal/provider"
)
func TestBuildRoute_HostsNotSet(t *testing.T) {
@@ -119,3 +127,97 @@ func TestBuildRoute_MetadataLabelsDoNotOverwriteControllerLabels(t *testing.T) {
assert.Equal(t, ar.Name, route.Labels["k8s/name"])
assert.Equal(t, "ApisixRoute/default/test-route", route.Labels["k8s/resource-key"])
}
+
+func TestTranslateApisixRouteStreamUpstreamScheme(t *testing.T) {
+ const (
+ namespace = "default"
+ serviceName = "backend"
+ portName = "tcp"
+ portNumber = int32(6000)
+ )
+
+ tests := []struct {
+ scheme string
+ protocol string
+ }{
+ {scheme: apiv2.SchemeTLS, protocol: "TCP"},
+ {scheme: apiv2.SchemeTCP, protocol: "TCP"},
+ {scheme: apiv2.SchemeUDP, protocol: "UDP"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.scheme, func(t *testing.T) {
+ translator := NewTranslator(logr.Discard(), "")
+ tctx := provider.NewDefaultTranslateContext(context.Background())
+
+ serviceKey := k8stypes.NamespacedName{Namespace: namespace, Name: serviceName}
+ tctx.Services[serviceKey] = &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: serviceName,
+ Namespace: namespace,
+ },
+ Spec: corev1.ServiceSpec{
+ Ports: []corev1.ServicePort{{
+ Name: portName,
+ Port: portNumber,
+ }},
+ },
+ }
+ tctx.EndpointSlices[serviceKey] = []discoveryv1.EndpointSlice{{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: serviceName + "-1",
+ Namespace: namespace,
+ },
+ Ports: []discoveryv1.EndpointPort{{
+ Name: ptr.To(portName),
+ Port: ptr.To(portNumber),
+ }},
+ Endpoints: []discoveryv1.Endpoint{{
+ Addresses: []string{"10.0.0.1"},
+ Conditions: discoveryv1.EndpointConditions{
+ Ready: ptr.To(true),
+ },
+ }},
+ }}
+ tctx.Upstreams[serviceKey] = &apiv2.ApisixUpstream{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: serviceName,
+ Namespace: namespace,
+ },
+ Spec: apiv2.ApisixUpstreamSpec{
+ ApisixUpstreamConfig: apiv2.ApisixUpstreamConfig{
+ Scheme: tt.scheme,
+ },
+ },
+ }
+
+ ar := &apiv2.ApisixRoute{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-route",
+ Namespace: namespace,
+ },
+ Spec: apiv2.ApisixRouteSpec{
+ Stream: []apiv2.ApisixRouteStream{{
+ Name: "rule1",
+ Protocol: tt.protocol,
+ Match: apiv2.ApisixRouteStreamMatch{
+ IngressPort: 8000,
+ },
+ Backend: apiv2.ApisixRouteStreamBackend{
+ ServiceName: serviceName,
+ ServicePort: intstr.FromInt32(portNumber),
+ },
+ }},
+ },
+ }
+
+ result, err := translator.TranslateApisixRoute(tctx, ar)
+ require.NoError(t, err)
+ require.Len(t, result.Services, 1)
+ require.NotNil(t, result.Services[0].Upstream)
+
+ assert.Equal(t, tt.scheme, result.Services[0].Upstream.Scheme)
+ assert.Equal(t, "10.0.0.1", result.Services[0].Upstream.Nodes[0].Host)
+ })
+ }
+}
diff --git a/internal/adc/translator/l4route_test.go b/internal/adc/translator/l4route_test.go
index fdf7a87d..c7901fe0 100644
--- a/internal/adc/translator/l4route_test.go
+++ b/internal/adc/translator/l4route_test.go
@@ -24,12 +24,18 @@ import (
"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ corev1 "k8s.io/api/core/v1"
+ discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/ptr"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
+ apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
"github.com/apache/apisix-ingress-controller/internal/provider"
+ internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
)
func TestTranslateTCPRouteWithL4RoutePolicy(t *testing.T) {
@@ -267,3 +273,85 @@ func TestTranslateTLSRouteWithL4RoutePolicy(t *testing.T) {
})
}
}
+
+func TestTranslateTCPRouteUpstreamScheme(t *testing.T) {
+ const (
+ namespace = "default"
+ serviceName = "backend"
+ portName = "tcp"
+ portNumber = int32(6000)
+ )
+
+ translator := NewTranslator(logr.Discard(), "")
+ tctx := provider.NewDefaultTranslateContext(context.Background())
+
+ serviceKey := k8stypes.NamespacedName{Namespace: namespace, Name: serviceName}
+ tctx.Services[serviceKey] = &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: serviceName,
+ Namespace: namespace,
+ },
+ Spec: corev1.ServiceSpec{
+ Ports: []corev1.ServicePort{{
+ Name: portName,
+ Port: portNumber,
+ }},
+ },
+ }
+ tctx.EndpointSlices[serviceKey] = []discoveryv1.EndpointSlice{{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: serviceName + "-1",
+ Namespace: namespace,
+ },
+ Ports: []discoveryv1.EndpointPort{{
+ Name: ptr.To(portName),
+ Port: ptr.To(portNumber),
+ }},
+ Endpoints: []discoveryv1.Endpoint{{
+ Addresses: []string{"10.0.0.1"},
+ Conditions: discoveryv1.EndpointConditions{
+ Ready: ptr.To(true),
+ },
+ }},
+ }}
+ tctx.BackendTrafficPolicies[serviceKey] = &v1alpha1.BackendTrafficPolicy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "backend-policy",
+ Namespace: namespace,
+ },
+ Spec: v1alpha1.BackendTrafficPolicySpec{
+ TargetRefs: []v1alpha1.BackendPolicyTargetReferenceWithSectionName{{
+ LocalPolicyTargetReference: gatewayv1.LocalPolicyTargetReference{
+ Name: gatewayv1.ObjectName(serviceName),
+ Kind: gatewayv1.Kind(internaltypes.KindService),
+ },
+ }},
+ Scheme: apiv2.SchemeTLS,
+ },
+ }
+
+ route := &gatewayv1alpha2.TCPRoute{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-tcp",
+ Namespace: namespace,
+ },
+ Spec: gatewayv1alpha2.TCPRouteSpec{
+ Rules: []gatewayv1alpha2.TCPRouteRule{{
+ BackendRefs: []gatewayv1alpha2.BackendRef{{
+ BackendObjectReference: gatewayv1.BackendObjectReference{
+ Name: gatewayv1.ObjectName(serviceName),
+ Port: ptr.To(portNumber),
+ },
+ }},
+ }},
+ },
+ }
+
+ result, err := translator.TranslateTCPRoute(tctx, route)
+ require.NoError(t, err)
+ require.Len(t, result.Services, 1)
+ require.NotNil(t, result.Services[0].Upstream)
+
+ assert.Equal(t, apiv2.SchemeTLS, result.Services[0].Upstream.Scheme)
+ assert.Equal(t, "10.0.0.1", result.Services[0].Upstream.Nodes[0].Host)
+}
diff --git a/internal/adc/translator/tcproute.go b/internal/adc/translator/tcproute.go
index 81e12a34..7327e911 100644
--- a/internal/adc/translator/tcproute.go
+++ b/internal/adc/translator/tcproute.go
@@ -68,7 +68,6 @@ func (t *Translator) TranslateTCPRoute(tctx *provider.TranslateContext, tcpRoute
if len(upNodes) == 0 {
continue
}
- // TODO: Confirm BackendTrafficPolicy attachment with e2e test case.
t.AttachBackendTrafficPolicyToUpstream(backend, tctx.BackendTrafficPolicies, upstream, tctx.Services)
upstream.Nodes = upNodes
var (
diff --git a/test/e2e/crds/v2/streamroute.go b/test/e2e/crds/v2/streamroute.go
index 151f6cd8..10ea85c1 100644
--- a/test/e2e/crds/v2/streamroute.go
+++ b/test/e2e/crds/v2/streamroute.go
@@ -25,6 +25,7 @@ import (
mqtt "github.com/eclipse/paho.mqtt.golang"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ "k8s.io/utils/ptr"
"github.com/apache/apisix-ingress-controller/test/e2e/framework"
"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
@@ -107,6 +108,61 @@ spec:
})
})
+ Context("TCP Proxy with TLS upstream", func() {
+ apisixUpstream := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ name: nginx
+spec:
+ ingressClassName: %s
+ scheme: tls
+`
+ apisixRoute := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ name: nginx-tcp-route
+spec:
+ ingressClassName: %s
+ stream:
+ - name: rule1
+ protocol: TCP
+ match:
+ ingressPort: 9100
+ backend:
+ serviceName: nginx
+ servicePort: 443
+`
+ BeforeEach(func() {
+ s.DeployNginx(framework.NginxOptions{
+ Namespace: s.Namespace(),
+ Replicas: ptr.To(int32(1)),
+ })
+ })
+
+ It("stream tcp proxy to tls upstream", func() {
+ // The client speaks plain TCP to APISIX; scheme: tls makes APISIX
+ // establish the TLS session with the upstream on its behalf.
+ err := s.CreateResourceFromString(fmt.Sprintf(apisixUpstream, s.Namespace()))
+ Expect(err).NotTo(HaveOccurred(), "creating ApisixUpstream")
+
+ err = s.CreateResourceFromString(fmt.Sprintf(apisixRoute, s.Namespace()))
+ Expect(err).NotTo(HaveOccurred(), "creating ApisixRoute")
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Client: s.NewAPISIXClientWithTCPProxy(),
+ Method: "GET",
+ Path: "/",
+ Checks: []scaffold.ResponseCheckFunc{
+ scaffold.WithExpectedStatus(200),
+ scaffold.WithExpectedHeader("X-Port", "443"),
+ scaffold.WithExpectedBodyContains("Hello, World!"),
+ },
+ })
+ })
+ })
+
Context("UDP Proxy", func() {
apisixRoute := `
apiVersion: apisix.apache.org/v2
diff --git a/test/e2e/gatewayapi/tcproute.go b/test/e2e/gatewayapi/tcproute.go
index 3af0d4ea..538f4aba 100644
--- a/test/e2e/gatewayapi/tcproute.go
+++ b/test/e2e/gatewayapi/tcproute.go
@@ -25,8 +25,10 @@ import (
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+ "github.com/apache/apisix-ingress-controller/test/e2e/framework"
"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
)
@@ -108,6 +110,96 @@ spec:
})
})
+ Context("TCPRoute With BackendTrafficPolicy", func() {
+ var tcpGateway = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+ name: %s
+spec:
+ gatewayClassName: %s
+ listeners:
+ - name: tcp
+ protocol: TCP
+ port: 80
+ allowedRoutes:
+ kinds:
+ - kind: TCPRoute
+ infrastructure:
+ parametersRef:
+ group: apisix.apache.org
+ kind: GatewayProxy
+ name: apisix-proxy-config
+`
+
+ var tcpRoute = `
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TCPRoute
+metadata:
+ name: tcp-tls-upstream
+spec:
+ parentRefs:
+ - name: %s
+ sectionName: tcp
+ rules:
+ - backendRefs:
+ - name: nginx
+ port: 443
+`
+
+ var backendTrafficPolicy = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+ name: nginx-tls
+spec:
+ targetRefs:
+ - name: nginx
+ kind: Service
+ group: ""
+ scheme: tls
+`
+
+ BeforeEach(func() {
+ if framework.ProviderType == framework.ProviderTypeAPI7EE {
+ // The API7 EE Admin API only accepts tcp/udp as the upstream scheme
+ // of a stream service.
+ Skip("scheme: tls is not supported by the API7 EE provider")
+ }
+ Expect(s.CreateResourceFromString(s.GetGatewayProxySpec())).NotTo(HaveOccurred(), "creating GatewayProxy")
+ Expect(s.CreateResourceFromString(s.GetGatewayClassYaml())).NotTo(HaveOccurred(), "creating GatewayClass")
+ Expect(s.CreateResourceFromString(fmt.Sprintf(tcpGateway, s.Namespace(), s.Namespace()))).
+ NotTo(HaveOccurred(), "creating Gateway")
+ s.DeployNginx(framework.NginxOptions{
+ Namespace: s.Namespace(),
+ Replicas: ptr.To(int32(1)),
+ })
+ })
+
+ It("BackendTrafficPolicy scheme tls connects to the upstream over TLS", func() {
+ By("creating BackendTrafficPolicy with scheme: tls")
+ Expect(s.CreateResourceFromString(backendTrafficPolicy)).NotTo(HaveOccurred(), "creating BackendTrafficPolicy")
+
+ By("creating TCPRoute to the TLS port of nginx")
+ s.ResourceApplied("TCPRoute", "tcp-tls-upstream", fmt.Sprintf(tcpRoute, s.Namespace()), 1)
+
+ // The client speaks plain HTTP over TCP; without scheme: tls nginx would
+ // reject the request on its TLS port instead of answering with 200.
+ s.RequestAssert(&scaffold.RequestAssert{
+ Client: s.NewAPISIXClientOnTCPPort(),
+ Method: "GET",
+ Path: "/",
+ Checks: []scaffold.ResponseCheckFunc{
+ scaffold.WithExpectedStatus(200),
+ scaffold.WithExpectedHeader("X-Port", "443"),
+ scaffold.WithExpectedBodyContains("Hello, World!"),
+ },
+ Timeout: time.Minute * 3,
+ Interval: time.Second * 2,
+ })
+ })
+ })
+
Context("TCPRoute With L4RoutePolicy", func() {
var tcpGateway = `
apiVersion: gateway.networking.k8s.io/v1