diff --git a/conformance/tests/httproute-backend-protocol-h2c.go b/conformance/tests/httproute-backend-protocol-h2c.go new file mode 100644 index 0000000000..82844a5c92 --- /dev/null +++ b/conformance/tests/httproute-backend-protocol-h2c.go @@ -0,0 +1,88 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "context" + "crypto/tls" + "net" + "testing" + + "golang.org/x/net/http2" + + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, HTTPRouteBackendProtocolH2C) +} + +var HTTPRouteBackendProtocolH2C = suite.ConformanceTest{ + ShortName: "HTTPRouteBackendProtocolH2C", + Description: "A HTTPRoute with a BackendRef that has an appProtocol kubernetes.io/h2c should be functional", + Features: []suite.SupportedFeature{ + suite.SupportGateway, + suite.SupportHTTPRoute, + suite.SupportHTTPRouteBackendProtocolH2C, + }, + Manifests: []string{ + "tests/httproute-backend-protocol-h2c.yaml", + }, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "backend-protocol-h2c", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + t.Run("http2 prior knowledge request should reach backend", func(t *testing.T) { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ + Request: http.Request{ + Path: "/h2c", + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, network, addr) + }, + }, + }, + Response: http.Response{StatusCode: 200}, + Backend: "h2c-backend", + Namespace: "gateway-conformance-web-backend", + }) + }) + + t.Run("h2c upgrade request should reach backend", func(t *testing.T) { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ + Request: http.Request{ + Path: "/h2c", + Headers: map[string]string{ + "HTTP2-Settings": "", + "Connection": "Upgrade, HTTP2-Settings", + }, + }, + Response: http.Response{StatusCode: 200}, + Backend: "h2c-backend", + Namespace: "gateway-conformance-web-backend", + }) + }) + }, +} diff --git a/conformance/tests/httproute-backend-protocol-h2c.yaml b/conformance/tests/httproute-backend-protocol-h2c.yaml new file mode 100644 index 0000000000..d50f408235 --- /dev/null +++ b/conformance/tests/httproute-backend-protocol-h2c.yaml @@ -0,0 +1,50 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: backend-protocol-h2c + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - name: h2c-backend + port: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: h2c-backend + namespace: gateway-conformance-infra +spec: + selector: + app: h2c-backend + ports: + - protocol: TCP + appProtocol: kubernetes.io/h2c + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: h2c-backend + namespace: gateway-conformance-infra + labels: + app: h2c-backend +spec: + replicas: 1 + selector: + matchLabels: + app: h2c-backend + template: + metadata: + labels: + app: h2c-backend + spec: + containers: + - name: h2c-backend + image: gcr.io/k8s-staging-gateway-api/echo-basic:latest + resources: + requests: + cpu: 10m diff --git a/conformance/utils/http/http.go b/conformance/utils/http/http.go index bfa2f25a69..3a2f8c35e5 100644 --- a/conformance/utils/http/http.go +++ b/conformance/utils/http/http.go @@ -19,6 +19,7 @@ package http import ( "fmt" "net" + "net/http" "net/url" "strings" "testing" @@ -69,6 +70,7 @@ type Request struct { Headers map[string]string UnfollowRedirect bool Protocol string + Transport http.RoundTripper } // ExpectedRequest defines expected properties of a request that reaches a backend. @@ -128,6 +130,7 @@ func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, sch Protocol: protocol, Headers: map[string][]string{}, UnfollowRedirect: expected.Request.UnfollowRedirect, + Transport: expected.Request.Transport, } if expected.Request.Headers != nil { diff --git a/conformance/utils/roundtripper/roundtripper.go b/conformance/utils/roundtripper/roundtripper.go index 5213ed5396..0b3e2b002b 100644 --- a/conformance/utils/roundtripper/roundtripper.go +++ b/conformance/utils/roundtripper/roundtripper.go @@ -49,6 +49,7 @@ type Request struct { CertPem []byte KeyPem []byte Server string + Transport http.RoundTripper } // String returns a printable version of Request for logging. Note that the @@ -117,25 +118,29 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques } } - transport := &http.Transport{ - DialContext: d.CustomDialContext, - // We disable keep-alives so that we don't leak established TCP connections. - // Leaking TCP connections is bad because we could eventually hit the - // threshold of maximum number of open TCP connections to a specific - // destination. Keep-alives are not presently utilized so disabling this has - // no adverse affect. - // - // Ref. https://github.com/kubernetes-sigs/gateway-api/issues/2357 - DisableKeepAlives: true, - } - if request.Server != "" && len(request.CertPem) != 0 && len(request.KeyPem) != 0 { - tlsConfig, err := tlsClientConfig(request.Server, request.CertPem, request.KeyPem) - if err != nil { - return nil, nil, err + if request.Transport != nil { + client.Transport = request.Transport + } else { + transport := &http.Transport{ + DialContext: d.CustomDialContext, + // We disable keep-alives so that we don't leak established TCP connections. + // Leaking TCP connections is bad because we could eventually hit the + // threshold of maximum number of open TCP connections to a specific + // destination. Keep-alives are not presently utilized so disabling this has + // no adverse affect. + // + // Ref. https://github.com/kubernetes-sigs/gateway-api/issues/2357 + DisableKeepAlives: true, + } + if request.Server != "" && len(request.CertPem) != 0 && len(request.KeyPem) != 0 { + tlsConfig, err := tlsClientConfig(request.Server, request.CertPem, request.KeyPem) + if err != nil { + return nil, nil, err + } + transport.TLSClientConfig = tlsConfig } - transport.TLSClientConfig = tlsConfig + client.Transport = transport } - client.Transport = transport method := "GET" if request.Method != "" { diff --git a/conformance/utils/suite/features.go b/conformance/utils/suite/features.go index a6d6051b76..dd3af071bf 100644 --- a/conformance/utils/suite/features.go +++ b/conformance/utils/suite/features.go @@ -121,6 +121,9 @@ const ( // This option indicates support for multiple RequestMirror filters within the same HTTPRoute rule (extended conformance). SupportHTTPRouteRequestMultipleMirrors SupportedFeature = "HTTPRouteRequestMultipleMirrors" + + // This option indicates support for HTTPRoute with a backendref with an appProtoocol 'kubernetes.io/h2c' + SupportHTTPRouteBackendProtocolH2C SupportedFeature = "HTTPRouteBackendProtocolH2C" ) // HTTPRouteExtendedFeatures includes all the supported features for HTTPRoute @@ -137,6 +140,7 @@ var HTTPRouteExtendedFeatures = sets.New( SupportHTTPRoutePathRewrite, SupportHTTPRouteRequestMirror, SupportHTTPRouteRequestMultipleMirrors, + SupportHTTPRouteBackendProtocolH2C, ) // -----------------------------------------------------------------------------